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

nl.topicus.jdbc.DDLStatement Maven / Gradle / Ivy

There is a newer version: 1.1.6
Show newest version
package nl.topicus.jdbc;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import nl.topicus.jdbc.shaded.com.google.rpc.Code;

import nl.topicus.jdbc.exception.CloudSpannerSQLException;

class DDLStatement
{
	private static final Pattern IF_EXISTS_PATTERN = Pattern.compile("(?i)if\\s+exists");
	private static final Pattern IF_NOT_EXISTS_PATTERN = Pattern.compile("(?i)if\\s+not\\s+exists");

	private static final String CREATE_KEYWORD = "create";
	private static final String DROP_KEYWORD = "drop";
	private static final String TABLE_KEYWORD = "table";
	private static final String INDEX_KEYWORD = "index";
	private static final String UNIQUE_KEYWORD = "unique";
	private static final String NULL_FILTERED_KEYWORD = "null_filtered";
	private static final String EXISTS_KEYWORD = "exists";
	private static final String IF_KEYWORD = "if";
	private static final String NOT_KEYWORD = "not";

	enum Command
	{
		UNKNOWN, DROP, CREATE;
	}

	enum ObjectType
	{
		UNKNOWN
		{
			@Override
			boolean exists(CloudSpannerConnection connection, String objectName) throws SQLException
			{
				return false;
			}
		},
		TABLE
		{
			@Override
			boolean exists(CloudSpannerConnection connection, String objectName) throws SQLException
			{
				try (ResultSet rs = connection.getMetaData().getTables("", "", objectName, null))
				{
					return rs.next();
				}
			}
		},
		INDEX
		{
			@Override
			boolean exists(CloudSpannerConnection connection, String objectName) throws SQLException
			{
				try (ResultSet rs = connection.getMetaData().getIndexInfo("", "", objectName))
				{
					return rs.next();
				}
			}
		};

		abstract boolean exists(CloudSpannerConnection connection, String objectName) throws SQLException;
	}

	enum ExistsStatement
	{
		NONE
		{
			@Override
			String removeExistsStatement(String sql)
			{
				return sql;
			}

			@Override
			boolean shouldExecute(boolean exists)
			{
				return false;
			}
		},
		IF_EXISTS
		{
			@Override
			String removeExistsStatement(String sql)
			{
				return IF_EXISTS_PATTERN.matcher(sql).replaceFirst(" ");
			}

			@Override
			boolean shouldExecute(boolean exists)
			{
				return exists;
			}
		},
		IF_NOT_EXISTS
		{
			@Override
			String removeExistsStatement(String sql)
			{
				return IF_NOT_EXISTS_PATTERN.matcher(sql).replaceFirst(" ");
			}

			@Override
			boolean shouldExecute(boolean exists)
			{
				return !exists;
			}
		};
		abstract String removeExistsStatement(String sql);

		abstract boolean shouldExecute(boolean exists);
	}

	private final Command command;

	private final ObjectType objectType;

	private final ExistsStatement existsStatement;

	private final String objectName;

	private final String sql;

	private DDLStatement(Command command, ObjectType objectType, ExistsStatement existsStatement, String objectName,
			String sql)
	{
		this.command = command;
		this.objectType = objectType;
		this.existsStatement = existsStatement;
		this.objectName = objectName;
		this.sql = sql;
	}

	static List parseDdlStatements(List sqlList) throws SQLException
	{
		List res = new ArrayList<>(sqlList.size());
		for (String sql : sqlList)
		{
			// Max possible length that we want to check is:
			// 'CREATE UNIQUE NULL_FILTERED INDEX IF NOT EXISTS index_name'
			List tokens = getTokens(sql, 8);
			// Shortest possible string that we want to check is:
			// 'DROP TABLE IF EXISTS table_name
			if (tokens.size() >= 3)
			{
				boolean create = tokens.get(0).equalsIgnoreCase(CREATE_KEYWORD);
				boolean drop = tokens.get(0).equalsIgnoreCase(DROP_KEYWORD);
				boolean table = tokens.get(1).equalsIgnoreCase(TABLE_KEYWORD);
				boolean index = tokens.get(1).equalsIgnoreCase(INDEX_KEYWORD);
				boolean uniqueIndex = tokens.get(1).equalsIgnoreCase(UNIQUE_KEYWORD)
						&& tokens.get(2).equalsIgnoreCase(INDEX_KEYWORD);
				boolean nullFilteredIndex = tokens.get(1).equalsIgnoreCase(NULL_FILTERED_KEYWORD)
						&& tokens.get(2).equalsIgnoreCase(INDEX_KEYWORD);
				boolean uniqueNullFilteredIndex = tokens.size() >= 4 && ((tokens.get(1).equalsIgnoreCase(UNIQUE_KEYWORD)
						&& tokens.get(2).equalsIgnoreCase(NULL_FILTERED_KEYWORD)
						&& tokens.get(3).equalsIgnoreCase(INDEX_KEYWORD))
						|| (tokens.get(1).equalsIgnoreCase(NULL_FILTERED_KEYWORD)
								&& tokens.get(2).equalsIgnoreCase(UNIQUE_KEYWORD)
								&& tokens.get(3).equalsIgnoreCase(INDEX_KEYWORD)));
				int currentIndex = 2;
				if (uniqueIndex || nullFilteredIndex)
					currentIndex = 3;
				else if (uniqueNullFilteredIndex)
					currentIndex = 4;
				// Check for exists statement
				boolean exists = tokens.size() > currentIndex + 2
						&& tokens.get(currentIndex).equalsIgnoreCase(IF_KEYWORD)
						&& tokens.get(currentIndex + 1).equalsIgnoreCase(EXISTS_KEYWORD);
				boolean notExists = tokens.size() > currentIndex + 3
						&& tokens.get(currentIndex).equalsIgnoreCase(IF_KEYWORD)
						&& tokens.get(currentIndex + 1).equalsIgnoreCase(NOT_KEYWORD)
						&& tokens.get(currentIndex + 2).equalsIgnoreCase(EXISTS_KEYWORD);
				if (exists)
					currentIndex += 2;
				if (notExists)
					currentIndex += 3;
				String name = tokens.size() > currentIndex ? tokens.get(currentIndex) : "";

				Command command;
				if (create)
					command = Command.CREATE;
				else if (drop)
					command = Command.DROP;
				else
					command = Command.UNKNOWN;

				ObjectType objectType;
				if (table)
					objectType = ObjectType.TABLE;
				else if (index || uniqueIndex || nullFilteredIndex || uniqueNullFilteredIndex)
					objectType = ObjectType.INDEX;
				else
					objectType = ObjectType.UNKNOWN;

				ExistsStatement existsStatement;
				if (exists)
					existsStatement = ExistsStatement.IF_EXISTS;
				else if (notExists)
					existsStatement = ExistsStatement.IF_NOT_EXISTS;
				else
					existsStatement = ExistsStatement.NONE;

				String commandSql = existsStatement.removeExistsStatement(sql);
				res.add(new DDLStatement(command, objectType, existsStatement, name, commandSql));
			}

		}
		return res;
	}

	private static List getTokens(String sql, int maxTokens) throws SQLException
	{
		List res = new ArrayList<>(maxTokens);
		int tokenNumber = 0;
		StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(sql));
		tokenizer.eolIsSignificant(false);
		tokenizer.wordChars('_', '_');
		tokenizer.wordChars('"', '"');
		tokenizer.wordChars('\'', '\'');
		tokenizer.quoteChar('`');
		try
		{
			while (tokenizer.nextToken() != StreamTokenizer.TT_EOF
					&& (tokenizer.ttype == StreamTokenizer.TT_WORD || tokenizer.ttype == '`')
					&& tokenNumber < maxTokens)
			{
				res.add(tokenizer.sval);
				tokenNumber++;
			}
		}
		catch (IOException e)
		{
			throw new CloudSpannerSQLException("Could not parse DDL statement '" + sql + "'. Error: " + e.getMessage(),
					Code.INVALID_ARGUMENT, e);
		}
		return res;
	}

	boolean shouldExecute(CloudSpannerConnection connection) throws SQLException
	{
		if (getExistsStatement() == null || getExistsStatement() == ExistsStatement.NONE)
			return true;
		if (getExistsStatement() == ExistsStatement.IF_NOT_EXISTS && getCommand() == Command.DROP)
			throw new CloudSpannerSQLException("Invalid argument: Cannot use 'IF NOT EXISTS' when dropping an object",
					Code.INVALID_ARGUMENT);
		if (getExistsStatement() == ExistsStatement.IF_EXISTS && getCommand() == Command.CREATE)
			throw new CloudSpannerSQLException("Invalid argument: Cannot use 'IF EXISTS' when creating an object",
					Code.INVALID_ARGUMENT);

		return getExistsStatement().shouldExecute(getObjectType().exists(connection, getObjectName()));
	}

	Command getCommand()
	{
		return command;
	}

	ObjectType getObjectType()
	{
		return objectType;
	}

	ExistsStatement getExistsStatement()
	{
		return existsStatement;
	}

	String getObjectName()
	{
		return objectName;
	}

	String getSql()
	{
		return sql;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy