/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.parser;
/**
* Inherit from this class in order to use the static methods without the class name
* to build patterns.
*/
public class SQLPatternFactory
{
//===== Flags used internally and from SQLPatternPart's to modify the resulting pattern
// Non-capturing group
static int GROUP = 0x0001;
// Capturing group
static int CAPTURE = 0x0002;
// Optional, implies at least a non-capturing group
static int OPTIONAL = 0x0004;
// Case-sensitive
static int CASE_SENSITIVE = 0x0008;
// Ignore new lines
static int IGNORE_NEW_LINE = 0x0010;
// Single line only
static int SINGLE_LINE = 0x0020;
// Insert leading whitespace
static int LEADING_SPACE = 0x0040;
// Insert leading whitespace before child parts
static int CHILD_SPACE_SEPARATOR = 0x0080;
// Set when it's a pure container, so that leading space is added to the child.
static int ADD_LEADING_SPACE_TO_CHILD = 0x0100;
//===== Public
// Use the SPF wrapper class as a abbreviated namespace to keep pattern construction
// as concise as possible while not polluting the derived class' namespace.
public static class SPF
{
public static SQLPatternPart statement(SQLPatternPart... parts)
{
return makeStatementPart(true, true, true, true, parts);
}
public static SQLPatternPart statementLeader(SQLPatternPart... parts)
{
return makeStatementPart(true, false, false, false, parts);
}
public static SQLPatternPart statementTrailer(SQLPatternPart... parts)
{
return makeStatementPart(false, true, true, false, parts);
}
public static SQLPatternPart clause(SQLPatternPart... parts)
{
SQLPatternPartElement retElem = new SQLPatternPartElement(parts);
retElem.m_flags |= SQLPatternFactory.GROUP;
retElem.m_flags |= SQLPatternFactory.CHILD_SPACE_SEPARATOR;
return retElem;
}
public static SQLPatternPart anyClause()
{
return new SQLPatternPartElement(".+");
}
/**
* Non-capturing group.
*/
public static SQLPatternPart group(SQLPatternPart part)
{
return makeGroup(false, null, part);
}
/**
* Capturing or non-capturing group
*/
public static SQLPatternPart group(boolean capture, SQLPatternPart part)
{
return makeGroup(capture, null, part);
}
/**
* Capturing or non-capturing group with capture label.
*/
public static SQLPatternPart group(boolean capture, String captureLabel, SQLPatternPart part)
{
return makeGroup(capture, captureLabel, part);
}
/**
* Capturing group.
*/
public static SQLPatternPart capture(SQLPatternPart part)
{
return makeGroup(true, null, part);
}
/**
* Capturing group with label.
*/
public static SQLPatternPart capture(String captureLabel, SQLPatternPart part)
{
return makeGroup(true, captureLabel, part);
}
public static SQLPatternPart optional(SQLPatternPart part)
{
part.m_flags |= SQLPatternFactory.OPTIONAL;
return part;
}
/// Unused/untested "one of" to support future OR-ing of multiple alternative clauses.
/// Please add unit tests, etc. when it is actually in use.
public static SQLPatternPart oneOf(SQLPatternPart... parts)
{
// Default to outer and inner non-capturing groups.
SQLPatternPartElement retElem = new SQLPatternPartElement(parts);
retElem.m_flags |= SQLPatternFactory.GROUP;
retElem.m_separator = "|";
return retElem;
}
public static SQLPatternPart oneOf(String... strs)
{
// Default to outer and inner non-capturing groups.
SQLPatternPartElement retElem = new SQLPatternPartElement(strs);
retElem.m_flags |= SQLPatternFactory.GROUP;
retElem.m_separator = "|";
return retElem;
}
public static SQLPatternPart token(String str)
{
return new SQLPatternPartElement(str);
}
public static SQLPatternPart dot()
{
return new SQLPatternPartElement("\\.");
}
public static SQLPatternPart tokenAlternatives(String... strs)
{
return oneOf(strs);
}
/*
* For table, column, index, view, etc. names.
*/
public static SQLPatternPart databaseObjectName()
{
//TODO: Does not recognize quoted identifiers.
return new SQLPatternPartElement("[\\w$]+");
}
public static SQLPatternPart databaseObjectTypeName()
{
return new SQLPatternPartElement("[a-z][a-z]*");
}
public static SQLPatternPart procedureName()
{
//TODO: Does not recognize quoted identifiers.
// Accepts '.', but they get rejected in the code with a clear error message.
return new SQLPatternPartElement("[\\w.$]+");
}
public static SQLPatternPart functionName()
{
return new SQLPatternPartElement("[\\w$]+");
}
public static SQLPatternPart classPath()
{
return new SQLPatternPartElement("(?:\\w+\\.)*\\w+");
}
public static SQLPatternPart languageName()
{
//TODO: Does not recognize quoted identifiers.
return new SQLPatternPartElement("[\\w.$]+");
}
public static SQLPatternPart userName()
{
return new SQLPatternPartElement("[\\w.$]+");
}
public static SQLPatternPart className()
{
return new SQLPatternPartElement("[\\w.$]+");
}
public static SQLPatternPart anythingOrNothing()
{
return new SQLPatternPartElement(".*");
}
public static SQLPatternPart integer()
{
return new SQLPatternPartElement("\\d+");
}
/**
* One or more repetitions of a pattern separated by a comma.
*/
public static SQLPatternPart commaList(SQLPatternPart part)
{
String itemExpr = part.generateExpression(0);
String listExpr = String.format("%s(?:\\s*,\\s*%s)*", itemExpr, itemExpr);
return new SQLPatternPartElement(listExpr);
}
public static SQLPatternPart delimitedCaptureBlock(String delimiter, String captureLabel)
{
return new SQLPatternPartElement(
new SQLPatternPartElement(delimiter),
makeGroup(true, captureLabel, anyClause()),
new SQLPatternPartElement(delimiter)
);
}
/**
* Repetition modifier without min/max.
*
* @param part Part that can repeat
* @return Part wrapper with added repetition
*/
public static SQLPatternPart repeat(SQLPatternPart part) {
return repeat(0, null, part);
}
/**
* Repetition modifier with min and optional max. Null max is infinity.
*
* Choosest cleanest notation based on counts. Does worry about combinations
* like (0,1) that could be handled another way, e.g. using optional().
* Asserts on negative numbers, max == 0, or max < min.
*
* @param minCount Min count (>=0)
* @param maxCount Max count (>0 and >= min count) if not null or infinity if null
* @param part Part that can repeat
* @return Part wrapper with added repetition
*/
public static SQLPatternPart repeat(int minCount, Integer maxCount, SQLPatternPart part)
{
assert minCount >= 0;
assert maxCount == null || (maxCount > 0 && maxCount >= minCount);
SQLPatternPartElement retElem = new SQLPatternPartElement(part);
SQLPatternPart retPart = retElem;
retElem.m_flags |= SQLPatternFactory.GROUP;
if (maxCount != null) {
// At least min count, but not more than max count.
retElem.m_trailer = String.format("{%d,%d}", minCount, maxCount);
}
else {
// No max - choose cleanest notation based on the min count.
if (minCount <= 0) {
retElem.m_trailer = String.format("*", minCount);
}
else if (minCount == 1) {
retElem.m_trailer = String.format("+", minCount);
}
else {
retElem.m_trailer = String.format("{%d,}", minCount);
}
}
return retPart;
}
public static SQLPatternPart anyColumnFields() {
return new SQLPatternPartElement("\\((?:.+?)\\)");
}
}
//===== Private methods
/**
* Make a capturing or non-capturing group
*/
private static SQLPatternPart makeGroup(boolean capture, String captureLabel, SQLPatternPart part)
{
// Need an outer part if capturing something that's already a group (capturing or not)
boolean alreadyGroup = (part.m_flags & (SQLPatternFactory.GROUP | SQLPatternFactory.CAPTURE)) != 0;
SQLPatternPart retPart = alreadyGroup ? new SQLPatternPartElement(part) : part;
if (capture) {
retPart.m_flags |= SQLPatternFactory.CAPTURE;
retPart.setCaptureLabel(captureLabel);
}
else {
retPart.m_flags |= SQLPatternFactory.GROUP;
}
return retPart;
}
private static SQLPatternPartElement makeStatementPart(
boolean beginLine,
boolean endLine,
boolean terminated,
boolean terminatorRequired,
SQLPatternPart... parts)
{
SQLPatternPartElement retElem = new SQLPatternPartElement(parts);
if (beginLine) {
retElem.m_leader = "\\A\\s*";
}
else {
retElem.m_leader = "\\A.*?";
}
if (endLine) {
if (terminated) {
if (terminatorRequired) {
retElem.m_trailer = "\\s*;\\s*\\z";
}
else {
retElem.m_trailer = "\\s*;?\\s*\\z";
}
}
else {
retElem.m_trailer = "\\s*\\z";
}
}
else {
retElem.m_trailer = ".*\\z";
}
retElem.m_flags |= SQLPatternFactory.CHILD_SPACE_SEPARATOR;
return retElem;
}
}