All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.datanucleus.query.JDOQLSingleStringParser Maven / Gradle / Ivy

Go to download

DataNucleus Core provides the primary components of a heterogenous Java persistence solution. It supports persistence API's being layered on top of the core functionality.

There is a newer version: 6.0.7
Show newest version
/**********************************************************************
Copyright (c) 2005 Andy Jefferson and others. 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. 


Contributors:
    ...
**********************************************************************/
package org.datanucleus.query;

import java.util.StringTokenizer;

import org.datanucleus.ClassConstants;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.store.query.Query;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;

/**
 * Parser for handling JDOQL Single-String queries.
 * Takes a JDOQLQuery and the query string and parses it into its constituent parts, updating
 * the JDOQLQuery accordingly with the result that after calling the parse() method the JDOQLQuery
 * is populated.
 * 
 * select [unique] [{result}] [into {result-class-name}]
 *                              [from {candidate-class-name} [exclude subclasses] ]
 *                              [where {filter}]
 *                              [variables {variables-clause} ]
 *                              [parameters {parameters-clause} ]
 *                              [{imports-clause}]
 *                              [group by {grouping-clause} ]
 *                              [order by {ordering-clause} ]
 *                              [range {from-range} ,{to-range}]                                       
 * 
* or *
 * UPDATE {candidate-class-name} SET fld1 = val[, fld2 = val2] WHERE {filter}
 * 
* or *
 * DELETE FROM {candidate-class-name} [exclude-subclasses] WHERE {filter}
 * 
* Note that {filter} can contain subqueries, hence containing keywords *
 * SELECT c FROM Customer c WHERE timeAvailable < (SELECT avg(hours) FROM Employee e)
 * 
* So the "filter" for the outer query is "timeAvailable < (SELECT avg(hours) FROM Employee e)" */ public class JDOQLSingleStringParser { /** The JDOQL query to populate. */ private Query query; /** The single-string query string. */ private String queryString; boolean allowDelete = false; boolean allowUpdate = false; /** * Constructor for the Single-String parser. * @param query The query * @param queryString The Single-String query */ public JDOQLSingleStringParser(Query query, String queryString) { NucleusLogger.QUERY.debug(Localiser.msg("042010", queryString)); this.query = query; this.queryString = queryString; } public void setAllowDelete(boolean allow) { allowDelete = allow; } public void setAllowUpdate(boolean allow) { allowUpdate = allow; } /** * Method to parse the Single-String query */ public void parse() { new Compiler(new Parser(queryString, allowDelete || allowUpdate)).compile(); } /** * Compiler to process keywords contents. In the query the keywords often have * content values following them that represent the constituent parts of the query. This takes the keyword * and sets the constituent part accordingly. */ private class Compiler { Parser tokenizer; Compiler(Parser tokenizer) { this.tokenizer = tokenizer; } private void compile() { compileSelect(); String keyword = tokenizer.parseKeyword(); if (keyword != null && JDOQLQueryHelper.isKeyword(keyword)) { // any keyword after compiling the SELECT is an error throw new NucleusUserException(Localiser.msg("042011", keyword)); } } private void compileSelect() { boolean update = false; boolean delete = false; if (allowUpdate && (tokenizer.parseKeyword("UPDATE") || tokenizer.parseKeyword("update"))) { update = true; query.setType(Query.BULK_UPDATE); } else if (allowDelete && (tokenizer.parseKeyword("DELETE") || tokenizer.parseKeyword("delete"))) { delete = true; query.setType(Query.BULK_DELETE); } else if (tokenizer.parseKeyword("SELECT") || tokenizer.parseKeyword("select")) { // Do nothing } else { throw new NucleusUserException(Localiser.msg("042012")); } if (update) { compileUpdate(); } else if (delete) { if (tokenizer.parseKeyword("FROM") || tokenizer.parseKeyword("from")) { compileFrom(); } } else { if (tokenizer.parseKeyword("UNIQUE") || tokenizer.parseKeyword("unique")) { compileUnique(); } compileResult(); if (tokenizer.parseKeyword("INTO") || tokenizer.parseKeyword("into")) { compileInto(); } if (tokenizer.parseKeyword("FROM") || tokenizer.parseKeyword("from")) { compileFrom(); } } if (tokenizer.parseKeyword("WHERE") || tokenizer.parseKeyword("where")) { compileWhere(); } if (tokenizer.parseKeyword("VARIABLES") || tokenizer.parseKeyword("variables")) { compileVariables(); } if (tokenizer.parseKeyword("PARAMETERS") || tokenizer.parseKeyword("parameters")) { compileParameters(); } if (tokenizer.parseKeyword("IMPORT") || tokenizer.parseKeyword("import")) { compileImport(); } if (tokenizer.parseKeyword("GROUP") || tokenizer.parseKeyword("group")) // GROUP BY { compileGroup(); } if (tokenizer.parseKeyword("ORDER") || tokenizer.parseKeyword("order")) // ORDER BY { compileOrder(); } if (tokenizer.parseKeyword("RANGE") || tokenizer.parseKeyword("range")) { compileRange(); } } private void compileUnique() { query.setUnique(true); } private void compileResult() { /* String content = tokenizer.parseContent(false); if (content.length() > 0) { query.setResult(content); }*/ String content = tokenizer.parseContent(true); if (content.length() > 0) { if (content.indexOf("SELECT ") > 0 || content.indexOf("select ") > 0) { // Subquery (or subqueries) present so split them out and just apply the result for this query String substitutedContent = processContentWithSubqueries(content); query.setResult(substitutedContent); } else { query.setResult(content); } } } private void compileUpdate() { String content = tokenizer.parseContent(false); if (content.length() == 0) { // No UPDATE clause throw new NucleusUserException(Localiser.msg("043010")); } query.setFrom(content); query.setCandidateClassName(content); if (tokenizer.parseKeyword("set") || tokenizer.parseKeyword("SET")) { content = tokenizer.parseContent(false); query.setUpdate(content.trim()); } else { // UPDATE clause has no "SET ..." ! throw new NucleusUserException(Localiser.msg("043011")); } } private void compileInto() { String content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "INTO", "")); } String resultClassName = content.trim(); query.setResultClassName(resultClassName); } private void compileFrom() { String content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "FROM", "")); } if (content.indexOf(' ') > 0) { // Subquery accepts " alias" query.setFrom(content.trim()); } else { // Content is "" query.setCandidateClassName(content); } if (tokenizer.parseKeyword("EXCLUDE") || tokenizer.parseKeyword("exclude")) { if (!tokenizer.parseKeyword("SUBCLASSES") && !tokenizer.parseKeyword("subclasses")) { throw new NucleusUserException(Localiser.msg("042015", "SUBCLASSES", "EXCLUDE")); } content = tokenizer.parseContent(false); if (content.length() > 0) { throw new NucleusUserException(Localiser.msg("042013", "EXCLUDE SUBCLASSES", content)); } query.setSubclasses(false); } } private void compileWhere() { String content = tokenizer.parseContent(true); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "WHERE", "")); } if (content.indexOf("SELECT ") > 0 || content.indexOf("select ") > 0) { // Subquery (or subqueries) present so split them out and just apply the filter for this query String substitutedContent = processContentWithSubqueries(content); query.setFilter(substitutedContent); } else { query.setFilter(content); } } /** * Method to extract the required clause, splitting out any subqueries and replacing by variables (adding subqueries to the underlying query), returning the clause to use. * @param content The input string * @return Content with subqueries substituted */ private String processContentWithSubqueries(String content) { StringBuilder stringContent = new StringBuilder(); boolean withinLiteralDouble = false; boolean withinLiteralSingle = false; int subqueryNum = 1; for (int i=0;i 0 && stringContent.charAt(stringContent.length()-1) != ' ') { stringContent.append(' '); } stringContent.append(subqueryVarName); i = endPosition; subqueryProcessed = true; subqueryNum++; } } } if (!subqueryProcessed) { stringContent.append(chr); } } if (withinLiteralDouble || withinLiteralSingle) { // Literal wasn't closed throw new NucleusUserException(Localiser.msg("042017")); } return stringContent.toString(); } private void compileVariables() { String content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "VARIABLES", "")); } query.declareExplicitVariables(content); } private void compileParameters() { String content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "PARAMETERS", "")); } query.declareExplicitParameters(content); } private void compileImport() { StringBuilder content = new StringBuilder("import " + tokenizer.parseContent(false)); while (tokenizer.parseKeyword("import")) { content.append("import ").append(tokenizer.parseContent(false)); } query.declareImports(content.toString()); } private void compileGroup() { String content = tokenizer.parseContent(false); if (!tokenizer.parseKeyword("BY") && !tokenizer.parseKeyword("by")) { // GROUP must be followed by BY throw new NucleusUserException(Localiser.msg("042015", "BY", "GROUP")); } content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "GROUP BY", "")); } query.setGrouping(content); } private void compileOrder() { String content = tokenizer.parseContent(false); if (!tokenizer.parseKeyword("BY") && !tokenizer.parseKeyword("by")) { // ORDER must be followed by BY throw new NucleusUserException(Localiser.msg("042015", "BY", "ORDER")); } content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "ORDER BY", "")); } query.setOrdering(content); } private void compileRange() { String content = tokenizer.parseContent(false); if (content.length() == 0) { // content cannot be empty throw new NucleusUserException(Localiser.msg("042014", "RANGE", "")); } query.setRange(content); } } /** * Tokenizer that provides access to current token. */ private static class Parser { final boolean extended; final String queryString; int queryStringPos = 0; /** tokens */ final String[] tokens; /** keywords */ final String[] keywords; /** current token cursor position */ int tokenIndex = -1; /** * Constructor * @param str String to parse */ public Parser(String str, boolean extended) { this.queryString = str; this.extended = extended; StringTokenizer tokenizer = new StringTokenizer(str); tokens = new String[tokenizer.countTokens()]; keywords = new String[tokenizer.countTokens()]; int i = 0; while (tokenizer.hasMoreTokens()) { tokens[i] = tokenizer.nextToken(); if ((extended && JDOQLQueryHelper.isKeywordExtended(tokens[i])) || (!extended && JDOQLQueryHelper.isKeyword(tokens[i]))) { keywords[i] = tokens[i]; } i++; } } /** * Parse the content until a keyword is found * @param allowSubentries Whether to permit subentries (in parentheses) in this next block * @return the content */ public String parseContent(boolean allowSubentries) { String content = ""; int level = 0; while (tokenIndex < tokens.length - 1) { tokenIndex++; if (allowSubentries) { // Process this token to check level of parentheses. // This is necessary because we want to ignore keywords if within a parentheses-block // e.g SELECT ... FROM ... WHERE param1 < (SELECT ... FROM ...) // and the "WHERE" part is "param1 < (SELECT ... FROM ...)" // Consequently subqueries will be parsed into the relevant block correctly. // Assumes that subqueries are placed in parentheses for (int i=0;i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy