org.apache.lucene
diff --git a/h2/src/docsrc/html/tutorial.html b/h2/src/docsrc/html/tutorial.html
index 1f0e6f535d..3dadf0f822 100644
--- a/h2/src/docsrc/html/tutorial.html
+++ b/h2/src/docsrc/html/tutorial.html
@@ -789,6 +789,15 @@ Using a Servlet Listener to Start and Stop a Database
</listener>
+If your servlet container is already Servlet 5-compatible, use the following
+snippet instead:
+
+
+<listener>
+ <listener-class>org.h2.server.web.JakartaDbStarter</listener-class>
+</listener>
+
+
For details on how to access the database, see the file DbStarter.java.
By default this tool opens an embedded connection
using the database URL jdbc:h2:~/test,
@@ -873,6 +882,10 @@
Using the H2 Console Servlet
For details, see also src/tools/WEB-INF/web.xml.
+If your application is already Servlet 5-compatible, use the servlet class
+org.h2.server.web.JakartaWebServlet instead.
+
+
To create a web application with just the H2 Console, run the following command:
diff --git a/h2/src/main/META-INF/MANIFEST.MF b/h2/src/main/META-INF/MANIFEST.MF
index 087eb4cc2a..c4a0ae3b15 100644
--- a/h2/src/main/META-INF/MANIFEST.MF
+++ b/h2/src/main/META-INF/MANIFEST.MF
@@ -28,6 +28,8 @@ Import-Package: javax.crypto,
javax.security.auth.login;resolution:=optional,
javax.servlet;resolution:=optional,
javax.servlet.http;resolution:=optional,
+ jakarta.servlet;resolution:=optional,
+ jakarta.servlet.http;resolution:=optional,
javax.sql,
javax.tools;resolution:=optional,
javax.transaction.xa;resolution:=optional,
diff --git a/h2/src/main/org/h2/server/web/JakartaDbStarter.java b/h2/src/main/org/h2/server/web/JakartaDbStarter.java
new file mode 100644
index 0000000000..1547672b97
--- /dev/null
+++ b/h2/src/main/org/h2/server/web/JakartaDbStarter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
+ * and the EPL 1.0 (https://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.h2.server.web;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletContextListener;
+
+import org.h2.tools.Server;
+import org.h2.util.StringUtils;
+
+/**
+ * This class can be used to start the H2 TCP server (or other H2 servers, for
+ * example the PG server) inside a Jakarta web application container such as
+ * Tomcat or Jetty. It can also open a database connection.
+ */
+public class JakartaDbStarter implements ServletContextListener {
+
+ private Connection conn;
+ private Server server;
+
+ @Override
+ public void contextInitialized(ServletContextEvent servletContextEvent) {
+ try {
+ org.h2.Driver.load();
+
+ // This will get the setting from a context-param in web.xml if
+ // defined:
+ ServletContext servletContext = servletContextEvent.getServletContext();
+ String url = getParameter(servletContext, "db.url", "jdbc:h2:~/test");
+ String user = getParameter(servletContext, "db.user", "sa");
+ String password = getParameter(servletContext, "db.password", "sa");
+
+ // Start the server if configured to do so
+ String serverParams = getParameter(servletContext, "db.tcpServer", null);
+ if (serverParams != null) {
+ String[] params = StringUtils.arraySplit(serverParams, ' ', true);
+ server = Server.createTcpServer(params);
+ server.start();
+ }
+
+ // To access the database in server mode, use the database URL:
+ // jdbc:h2:tcp://localhost/~/test
+ conn = DriverManager.getConnection(url, user, password);
+ servletContext.setAttribute("connection", conn);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static String getParameter(ServletContext servletContext,
+ String key, String defaultValue) {
+ String value = servletContext.getInitParameter(key);
+ return value == null ? defaultValue : value;
+ }
+
+ /**
+ * Get the connection.
+ *
+ * @return the connection
+ */
+ public Connection getConnection() {
+ return conn;
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent servletContextEvent) {
+ try {
+ Statement stat = conn.createStatement();
+ stat.execute("SHUTDOWN");
+ stat.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ try {
+ conn.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (server != null) {
+ server.stop();
+ server = null;
+ }
+ }
+
+}
diff --git a/h2/src/main/org/h2/server/web/JakartaWebServlet.java b/h2/src/main/org/h2/server/web/JakartaWebServlet.java
new file mode 100644
index 0000000000..260266e0e1
--- /dev/null
+++ b/h2/src/main/org/h2/server/web/JakartaWebServlet.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
+ * and the EPL 1.0 (https://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.h2.server.web;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.h2.util.NetworkConnectionInfo;
+
+/**
+ * This servlet lets the H2 Console be used in a Jakarta servlet container
+ * such as Tomcat or Jetty.
+ */
+public class JakartaWebServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+ private transient WebServer server;
+
+ @Override
+ public void init() {
+ ServletConfig config = getServletConfig();
+ Enumeration> en = config.getInitParameterNames();
+ ArrayList list = new ArrayList<>();
+ while (en.hasMoreElements()) {
+ String name = en.nextElement().toString();
+ String value = config.getInitParameter(name);
+ if (!name.startsWith("-")) {
+ name = "-" + name;
+ }
+ list.add(name);
+ if (value.length() > 0) {
+ list.add(value);
+ }
+ }
+ String[] args = list.toArray(new String[0]);
+ server = new WebServer();
+ server.setAllowChunked(false);
+ server.init(args);
+ }
+
+ @Override
+ public void destroy() {
+ server.stop();
+ }
+
+ private boolean allow(HttpServletRequest req) {
+ if (server.getAllowOthers()) {
+ return true;
+ }
+ String addr = req.getRemoteAddr();
+ try {
+ InetAddress address = InetAddress.getByName(addr);
+ return address.isLoopbackAddress();
+ } catch (UnknownHostException | NoClassDefFoundError e) {
+ // Google App Engine does not allow java.net.InetAddress
+ return false;
+ }
+
+ }
+
+ private String getAllowedFile(HttpServletRequest req, String requestedFile) {
+ if (!allow(req)) {
+ return "notAllowed.jsp";
+ }
+ if (requestedFile.length() == 0) {
+ return "index.do";
+ }
+ return requestedFile;
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+ req.setCharacterEncoding("utf-8");
+ String file = req.getPathInfo();
+ if (file == null) {
+ resp.sendRedirect(req.getRequestURI() + "/");
+ return;
+ } else if (file.startsWith("/")) {
+ file = file.substring(1);
+ }
+ file = getAllowedFile(req, file);
+
+ // extract the request attributes
+ Properties attributes = new Properties();
+ Enumeration> en = req.getAttributeNames();
+ while (en.hasMoreElements()) {
+ String name = en.nextElement().toString();
+ String value = req.getAttribute(name).toString();
+ attributes.put(name, value);
+ }
+ en = req.getParameterNames();
+ while (en.hasMoreElements()) {
+ String name = en.nextElement().toString();
+ String value = req.getParameter(name);
+ attributes.put(name, value);
+ }
+
+ WebSession session = null;
+ String sessionId = attributes.getProperty("jsessionid");
+ if (sessionId != null) {
+ session = server.getSession(sessionId);
+ }
+ WebApp app = new WebApp(server);
+ app.setSession(session, attributes);
+ String ifModifiedSince = req.getHeader("if-modified-since");
+
+ String scheme = req.getScheme();
+ StringBuilder builder = new StringBuilder(scheme).append("://").append(req.getServerName());
+ int serverPort = req.getServerPort();
+ if (!(serverPort == 80 && scheme.equals("http") || serverPort == 443 && scheme.equals("https"))) {
+ builder.append(':').append(serverPort);
+ }
+ String path = builder.append(req.getContextPath()).toString();
+ file = app.processRequest(file, new NetworkConnectionInfo(path, req.getRemoteAddr(), req.getRemotePort()));
+ session = app.getSession();
+
+ String mimeType = app.getMimeType();
+ boolean cache = app.getCache();
+
+ if (cache && server.getStartDateTime().equals(ifModifiedSince)) {
+ resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ return;
+ }
+ byte[] bytes = server.getFile(file);
+ if (bytes == null) {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ bytes = ("File not found: " + file).getBytes(StandardCharsets.UTF_8);
+ } else {
+ if (session != null && file.endsWith(".jsp")) {
+ String page = new String(bytes, StandardCharsets.UTF_8);
+ page = PageParser.parse(page, session.map);
+ bytes = page.getBytes(StandardCharsets.UTF_8);
+ }
+ resp.setContentType(mimeType);
+ if (!cache) {
+ resp.setHeader("Cache-Control", "no-cache");
+ } else {
+ resp.setHeader("Cache-Control", "max-age=10");
+ resp.setHeader("Last-Modified", server.getStartDateTime());
+ }
+ }
+ if (bytes != null) {
+ ServletOutputStream out = resp.getOutputStream();
+ out.write(bytes);
+ }
+ }
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+ doGet(req, resp);
+ }
+
+}
diff --git a/h2/src/test/org/h2/test/TestAll.java b/h2/src/test/org/h2/test/TestAll.java
index 92ca79837e..4c5c3e5baf 100644
--- a/h2/src/test/org/h2/test/TestAll.java
+++ b/h2/src/test/org/h2/test/TestAll.java
@@ -122,6 +122,7 @@
import org.h2.test.scripts.TestScript;
import org.h2.test.server.TestAutoServer;
import org.h2.test.server.TestInit;
+import org.h2.test.server.TestJakartaWeb;
import org.h2.test.server.TestNestedLoop;
import org.h2.test.server.TestWeb;
import org.h2.test.store.TestCacheConcurrentLIRS;
@@ -835,6 +836,7 @@ private void test() throws SQLException {
addTest(new TestQueryCache());
addTest(new TestUrlJavaObjectSerializer());
addTest(new TestWeb());
+ addTest(new TestJakartaWeb());
// other unsafe
addTest(new TestOptimizations());
@@ -872,6 +874,7 @@ private void testAdditional() {
addTest(new TestRecovery());
addTest(new RecoverLobTest());
addTest(createTest("org.h2.test.unit.TestServlet"));
+ addTest(createTest("org.h2.test.unit.TestJakartaServlet"));
addTest(new TestTimeStampWithTimeZone());
addTest(new TestValue());
diff --git a/h2/src/test/org/h2/test/server/TestJakartaWeb.java b/h2/src/test/org/h2/test/server/TestJakartaWeb.java
new file mode 100644
index 0000000000..7d24757915
--- /dev/null
+++ b/h2/src/test/org/h2/test/server/TestJakartaWeb.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
+ * and the EPL 1.0 (https://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.h2.test.server;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Vector;
+
+import jakarta.servlet.AsyncContext;
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.http.HttpUpgradeHandler;
+import jakarta.servlet.http.Part;
+
+import org.h2.server.web.JakartaWebServlet;
+import org.h2.test.TestBase;
+import org.h2.test.TestDb;
+import org.h2.util.Utils10;
+
+/**
+ * Tests the Jakarta Web Servlet for the H2 Console.
+ */
+public class TestJakartaWeb extends TestDb {
+
+ /**
+ * Run just this test.
+ *
+ * @param a ignored
+ */
+ public static void main(String... a) throws Exception {
+ TestBase.createCaller().init().testFromMain();
+ }
+
+ @Override
+ public void test() throws Exception {
+ testServlet();
+ }
+
+ private void testServlet() throws Exception {
+ JakartaWebServlet servlet = new JakartaWebServlet();
+ final HashMap configMap = new HashMap<>();
+ configMap.put("ifExists", "");
+ configMap.put("", "");
+ ServletConfig config = new ServletConfig() {
+
+ @Override
+ public String getServletName() {
+ return "H2Console";
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ return new Vector<>(configMap.keySet()).elements();
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ return configMap.get(name);
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return null;
+ }
+
+ };
+ servlet.init(config);
+
+
+ TestHttpServletRequest request = new TestHttpServletRequest();
+ request.setPathInfo("/");
+ TestHttpServletResponse response = new TestHttpServletResponse();
+ TestServletOutputStream out = new TestServletOutputStream();
+ response.setServletOutputStream(out);
+ servlet.doGet(request, response);
+ assertContains(out.toString(), "location.href = 'login.jsp");
+ servlet.destroy();
+ }
+
+ /**
+ * A HTTP servlet request for testing.
+ */
+ static class TestHttpServletRequest implements HttpServletRequest {
+
+ private String pathInfo;
+
+ void setPathInfo(String pathInfo) {
+ this.pathInfo = pathInfo;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return null;
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return new Vector().elements();
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ @Override
+ public int getContentLength() {
+ return 0;
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return null;
+ }
+
+ @Override
+ public String getLocalName() {
+ return null;
+ }
+
+ @Override
+ public int getLocalPort() {
+ return 0;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getLocales() {
+ return null;
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return null;
+ }
+
+ @Override
+ public Map getParameterMap() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getParameterNames() {
+ return new Vector().elements();
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ return null;
+ }
+
+ @Override
+ public String getProtocol() {
+ return null;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public String getRealPath(String path) {
+ return null;
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return null;
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return null;
+ }
+
+ @Override
+ public int getRemotePort() {
+ return 0;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String name) {
+ return null;
+ }
+
+ @Override
+ public String getScheme() {
+ return "http";
+ }
+
+ @Override
+ public String getServerName() {
+ return null;
+ }
+
+ @Override
+ public int getServerPort() {
+ return 80;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ // ignore
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ // ignore
+ }
+
+ @Override
+ public void setCharacterEncoding(String encoding)
+ throws UnsupportedEncodingException {
+ // ignore
+ }
+
+ @Override
+ public String getAuthType() {
+ return null;
+ }
+
+ @Override
+ public String getContextPath() {
+ return null;
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return null;
+ }
+
+ @Override
+ public long getDateHeader(String x) {
+ return 0;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return null;
+ }
+
+ @Override
+ public Enumeration getHeaderNames() {
+ return null;
+ }
+
+ @Override
+ public Enumeration getHeaders(String name) {
+ return null;
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ return 0;
+ }
+
+ @Override
+ public String getMethod() {
+ return null;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return pathInfo;
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return null;
+ }
+
+ @Override
+ public String getQueryString() {
+ return null;
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return null;
+ }
+
+ @Override
+ public String getRequestURI() {
+ return null;
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return null;
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return null;
+ }
+
+ @Override
+ public String getServletPath() {
+ return null;
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return null;
+ }
+
+ @Override
+ public HttpSession getSession(boolean x) {
+ return null;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return null;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ return false;
+ }
+
+ @Override
+ @Deprecated
+ public boolean isRequestedSessionIdFromUrl() {
+ return false;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ return false;
+ }
+
+ @Override
+ public boolean isUserInRole(String x) {
+ return false;
+ }
+
+ @Override
+ public java.util.Collection getParts() {
+ return null;
+ }
+
+ @Override
+ public Part getPart(String name) {
+ return null;
+ }
+
+ @Override
+ public boolean authenticate(HttpServletResponse response) {
+ return false;
+ }
+
+ @Override
+ public void login(String username, String password) {
+ // ignore
+ }
+
+ @Override
+ public void logout() {
+ // ignore
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return null;
+ }
+
+ @Override
+ public AsyncContext startAsync() {
+ return null;
+ }
+
+ @Override
+ public AsyncContext startAsync(
+ ServletRequest servletRequest,
+ ServletResponse servletResponse) {
+ return null;
+ }
+
+ @Override
+ public boolean isAsyncStarted() {
+ return false;
+ }
+
+ @Override
+ public boolean isAsyncSupported() {
+ return false;
+ }
+
+ @Override
+ public AsyncContext getAsyncContext() {
+ return null;
+ }
+
+ @Override
+ public DispatcherType getDispatcherType() {
+ return null;
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return 0;
+ }
+
+ @Override
+ public String changeSessionId() {
+ return null;
+ }
+
+ @Override
+ public T upgrade(Class handlerClass)
+ throws IOException, ServletException {
+ return null;
+ }
+
+ }
+
+ /**
+ * A HTTP servlet response for testing.
+ */
+ static class TestHttpServletResponse implements HttpServletResponse {
+
+ ServletOutputStream servletOutputStream;
+
+ void setServletOutputStream(ServletOutputStream servletOutputStream) {
+ this.servletOutputStream = servletOutputStream;
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ // ignore
+ }
+
+ @Override
+ public int getBufferSize() {
+ return 0;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return null;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ return servletOutputStream;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ return null;
+ }
+
+ @Override
+ public boolean isCommitted() {
+ return false;
+ }
+
+ @Override
+ public void reset() {
+ // ignore
+ }
+
+ @Override
+ public void resetBuffer() {
+ // ignore
+ }
+
+ @Override
+ public void setBufferSize(int arg0) {
+ // ignore
+ }
+
+ @Override
+ public void setCharacterEncoding(String arg0) {
+ // ignore
+ }
+
+ @Override
+ public void setContentLength(int arg0) {
+ // ignore
+ }
+
+ @Override
+ public void setContentLengthLong(long arg0) {
+ // ignore
+ }
+
+ @Override
+ public void setContentType(String arg0) {
+ // ignore
+ }
+
+ @Override
+ public void setLocale(Locale arg0) {
+ // ignore
+ }
+
+ @Override
+ public void addCookie(Cookie arg0) {
+ // ignore
+ }
+
+ @Override
+ public void addDateHeader(String arg0, long arg1) {
+ // ignore
+ }
+
+ @Override
+ public void addHeader(String arg0, String arg1) {
+ // ignore
+ }
+
+ @Override
+ public void addIntHeader(String arg0, int arg1) {
+ // ignore
+ }
+
+ @Override
+ public boolean containsHeader(String arg0) {
+ return false;
+ }
+
+ @Override
+ public String encodeRedirectURL(String arg0) {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public String encodeRedirectUrl(String arg0) {
+ return null;
+ }
+
+ @Override
+ public String encodeURL(String arg0) {
+ return null;
+ }
+
+ @Override
+ @Deprecated
+ public String encodeUrl(String arg0) {
+ return null;
+ }
+
+ @Override
+ public void sendError(int arg0) throws IOException {
+ // ignore
+ }
+
+ @Override
+ public void sendError(int arg0, String arg1) throws IOException {
+ // ignore
+ }
+
+ @Override
+ public void sendRedirect(String arg0) throws IOException {
+ // ignore
+ }
+
+ @Override
+ public void setDateHeader(String arg0, long arg1) {
+ // ignore
+ }
+
+ @Override
+ public void setHeader(String arg0, String arg1) {
+ // ignore
+ }
+
+ @Override
+ public void setIntHeader(String arg0, int arg1) {
+ // ignore
+ }
+
+ @Override
+ public void setStatus(int arg0) {
+ // ignore
+ }
+
+ @Override
+ @Deprecated
+ public void setStatus(int arg0, String arg1) {
+ // ignore
+ }
+
+ @Override
+ public int getStatus() {
+ return 0;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return null;
+ }
+
+ @Override
+ public java.util.Collection getHeaders(String name) {
+ return null;
+ }
+
+ @Override
+ public java.util.Collection getHeaderNames() {
+ return null;
+ }
+
+ }
+
+ /**
+ * A servlet output stream for testing.
+ */
+ static class TestServletOutputStream extends ServletOutputStream {
+
+ private final ByteArrayOutputStream buff = new ByteArrayOutputStream();
+
+ @Override
+ public void write(int b) throws IOException {
+ buff.write(b);
+ }
+
+ @Override
+ public String toString() {
+ return Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ // ignore
+ }
+
+ }
+
+}
diff --git a/h2/src/test/org/h2/test/unit/TestJakartaServlet.java b/h2/src/test/org/h2/test/unit/TestJakartaServlet.java
new file mode 100644
index 0000000000..6f6cb83c03
--- /dev/null
+++ b/h2/src/test/org/h2/test/unit/TestJakartaServlet.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
+ * and the EPL 1.0 (https://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.h2.test.unit;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterRegistration;
+import jakarta.servlet.FilterRegistration.Dynamic;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletContextEvent;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRegistration;
+import jakarta.servlet.SessionCookieConfig;
+import jakarta.servlet.SessionTrackingMode;
+import jakarta.servlet.descriptor.JspConfigDescriptor;
+import org.h2.api.ErrorCode;
+import org.h2.server.web.JakartaDbStarter;
+import org.h2.test.TestBase;
+import org.h2.test.TestDb;
+
+/**
+ * Tests the JakartaDbStarter servlet.
+ * This test simulates a minimum servlet container environment.
+ */
+public class TestJakartaServlet extends TestDb {
+
+ /**
+ * Run just this test.
+ *
+ * @param a ignored
+ */
+ public static void main(String... a) throws Exception {
+ TestBase.createCaller().init().testFromMain();
+ }
+
+ /**
+ * Minimum ServletContext implementation.
+ * Most methods are not implemented.
+ */
+ static class TestServletContext implements ServletContext {
+
+ private final Properties initParams = new Properties();
+ private final HashMap attributes = new HashMap<>();
+
+ @Override
+ public void setAttribute(String key, Object value) {
+ attributes.put(key, value);
+ }
+
+ @Override
+ public Object getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ @Override
+ public boolean setInitParameter(String key, String value) {
+ initParams.setProperty(key, value);
+ return true;
+ }
+
+ @Override
+ public String getInitParameter(String key) {
+ return initParams.getProperty(key);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServletContext getContext(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getMajorVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getMimeType(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getMinorVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getRealPath(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public URL getResource(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set getResourcePaths(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getServerInfo() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @deprecated as of servlet API 2.1
+ */
+ @Override
+ @Deprecated
+ public Servlet getServlet(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getServletContextName() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @deprecated as of servlet API 2.1
+ */
+ @Deprecated
+ @Override
+ public Enumeration getServletNames() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @deprecated as of servlet API 2.0
+ */
+ @Deprecated
+ @Override
+ public Enumeration getServlets() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void log(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @deprecated as of servlet API 2.1
+ */
+ @Deprecated
+ @Override
+ public void log(Exception exception, String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void log(String string, Throwable throwable) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeAttribute(String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dynamic addFilter(String arg0, String arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dynamic addFilter(String arg0, Filter arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Dynamic addFilter(String arg0, Class extends Filter> arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addListener(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addListener(T arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addListener(Class extends EventListener> arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public jakarta.servlet.ServletRegistration.Dynamic addServlet(
+ String arg0, String arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public jakarta.servlet.ServletRegistration.Dynamic addServlet(
+ String arg0, Servlet arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public jakarta.servlet.ServletRegistration.Dynamic addServlet(
+ String arg0, Class extends Servlet> arg1) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T createFilter(Class arg0)
+ throws ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T createListener(Class arg0)
+ throws ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T createServlet(Class arg0)
+ throws ServletException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void declareRoles(String... arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getContextPath() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set getDefaultSessionTrackingModes() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getEffectiveMajorVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getEffectiveMinorVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Set getEffectiveSessionTrackingModes() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getFilterRegistrations() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map getServletRegistrations() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig() {
+ throw new UnsupportedOperationException();
+ }
+
+
+ @Override
+ public void setSessionTrackingModes(Set arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getVirtualServerName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getSessionTimeout() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setSessionTimeout(int sessionTimeout) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getRequestCharacterEncoding() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setRequestCharacterEncoding(String encoding) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getResponseCharacterEncoding() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setResponseCharacterEncoding(String encoding) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ @Override
+ public boolean isEnabled() {
+ if (config.networked || config.memory) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void test() throws SQLException {
+ JakartaDbStarter listener = new JakartaDbStarter();
+
+ TestServletContext context = new TestServletContext();
+ String url = getURL("servlet", true);
+ context.setInitParameter("db.url", url);
+ context.setInitParameter("db.user", getUser());
+ context.setInitParameter("db.password", getPassword());
+ context.setInitParameter("db.tcpServer", "-tcpPort 8888");
+
+ ServletContextEvent event = new ServletContextEvent(context);
+ listener.contextInitialized(event);
+
+ Connection conn1 = listener.getConnection();
+ Connection conn1a = (Connection) context.getAttribute("connection");
+ assertTrue(conn1 == conn1a);
+ Statement stat1 = conn1.createStatement();
+ stat1.execute("CREATE TABLE T(ID INT)");
+
+ String u2 = url.substring(url.indexOf("servlet"));
+ u2 = "jdbc:h2:tcp://localhost:8888/" + getBaseDir() + "/" + u2;
+ Connection conn2 = DriverManager.getConnection(
+ u2, getUser(), getPassword());
+ Statement stat2 = conn2.createStatement();
+ stat2.execute("SELECT * FROM T");
+ stat2.execute("DROP TABLE T");
+
+ assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat1).
+ execute("SELECT * FROM T");
+ conn2.close();
+
+ listener.contextDestroyed(event);
+
+ // listener must be stopped
+ assertThrows(ErrorCode.CONNECTION_BROKEN_1,
+ () -> getConnection("jdbc:h2:tcp://localhost:8888/" + getBaseDir() + "/servlet", getUser(),
+ getPassword()));
+
+ // connection must be closed
+ assertThrows(ErrorCode.OBJECT_CLOSED, stat1).
+ execute("SELECT * FROM DUAL");
+
+ deleteDb("servlet");
+
+ }
+
+}
diff --git a/h2/src/tools/org/h2/build/Build.java b/h2/src/tools/org/h2/build/Build.java
index f7018764de..7d23858a3b 100644
--- a/h2/src/tools/org/h2/build/Build.java
+++ b/h2/src/tools/org/h2/build/Build.java
@@ -57,7 +57,9 @@ public class Build extends BuildBase {
private static final String PGJDBC_HASH = "45fa6eef266aa80024ef2ab3688d9faa38c642e5";
- private static final String SERVLET_VERSION = "4.0.1";
+ private static final String JAVAX_SERVLET_VERSION = "4.0.1";
+
+ private static final String JAKARTA_SERVLET_VERSION = "5.0.0";
private static final String SLF4J_VERSION = "1.7.30";
@@ -143,7 +145,8 @@ public void compile() {
mkdir("temp");
download();
String classpath = "temp" +
- File.pathSeparator + "ext/javax.servlet-api-" + SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-core-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-analyzers-common-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-queryparser-" + LUCENE_VERSION + ".jar" +
@@ -258,7 +261,8 @@ public void coverage() {
delete(files("coverage/bin/META-INF/versions"));
String cp = "coverage/bin" +
File.pathSeparator + "ext/postgresql-" + PGJDBC_VERSION + ".jar" +
- File.pathSeparator + "ext/javax.servlet-api-" + SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-core-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-analyzers-common-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-queryparser-" + LUCENE_VERSION + ".jar" +
@@ -371,9 +375,12 @@ public void download() {
}
private void downloadOrVerify(boolean offline) {
- downloadOrVerify("ext/javax.servlet-api-" + SERVLET_VERSION + ".jar",
- "javax/servlet", "javax.servlet-api", SERVLET_VERSION,
+ downloadOrVerify("ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar",
+ "javax/servlet", "javax.servlet-api", JAVAX_SERVLET_VERSION,
"a27082684a2ff0bf397666c3943496c44541d1ca", offline);
+ downloadOrVerify("ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar",
+ "jakarta/servlet", "jakarta.servlet-api", JAKARTA_SERVLET_VERSION,
+ "2e6b8ccde55522c879434ddec3714683ccae6867", offline);
downloadOrVerify("ext/lucene-core-" + LUCENE_VERSION + ".jar",
"org/apache/lucene", "lucene-core", LUCENE_VERSION,
"b275ca5f39b6dd45d5a7ecb49da65205ad2732ca", offline);
@@ -609,7 +616,8 @@ public void javadocImpl() {
"-d", "docs/javadocImpl2",
"-classpath", javaToolsJar +
File.pathSeparator + "ext/slf4j-api-" + SLF4J_VERSION + ".jar" +
- File.pathSeparator + "ext/javax.servlet-api-" + SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-core-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-analyzers-common-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-queryparser-" + LUCENE_VERSION + ".jar" +
@@ -628,7 +636,8 @@ public void javadocImpl() {
"-d", "docs/javadocImpl3",
"-classpath", javaToolsJar +
File.pathSeparator + "ext/slf4j-api-" + SLF4J_VERSION + ".jar" +
- File.pathSeparator + "ext/javax.servlet-api-" + SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-core-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-analyzers-common-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-queryparser-" + LUCENE_VERSION + ".jar" +
@@ -646,7 +655,8 @@ public void javadocImpl() {
"-d", "docs/javadoc",
"-classpath", javaToolsJar +
File.pathSeparator + "ext/slf4j-api-" + SLF4J_VERSION + ".jar" +
- File.pathSeparator + "ext/javax.servlet-api-" + SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-core-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-analyzers-common-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-queryparser-" + LUCENE_VERSION + ".jar" +
@@ -872,7 +882,8 @@ private void test(boolean ci) {
downloadTest();
String cp = "temp" + File.pathSeparator + "bin" +
File.pathSeparator + "ext/postgresql-" + PGJDBC_VERSION + ".jar" +
- File.pathSeparator + "ext/javax.servlet-api-" + SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/javax.servlet-api-" + JAVAX_SERVLET_VERSION + ".jar" +
+ File.pathSeparator + "ext/jakarta.servlet-api-" + JAKARTA_SERVLET_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-core-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-analyzers-common-" + LUCENE_VERSION + ".jar" +
File.pathSeparator + "ext/lucene-queryparser-" + LUCENE_VERSION + ".jar" +
From ea265d3c2a101fe8a2b0b1309b5fd9ae229ce286 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Tue, 4 Jan 2022 13:36:25 +0800
Subject: [PATCH 33/73] Tokenize SQL before parsing
---
.../main/org/h2/command/CommandContainer.java | 3 +-
h2/src/main/org/h2/command/Parser.java | 1653 +++--------------
h2/src/main/org/h2/command/Prepared.java | 20 +-
h2/src/main/org/h2/command/Token.java | 757 ++++++++
h2/src/main/org/h2/command/Tokenizer.java | 815 ++++++++
h2/src/main/org/h2/util/StringUtils.java | 10 +-
.../test/org/h2/test/unit/TestKeywords.java | 13 +-
7 files changed, 1900 insertions(+), 1371 deletions(-)
create mode 100644 h2/src/main/org/h2/command/Token.java
create mode 100644 h2/src/main/org/h2/command/Tokenizer.java
diff --git a/h2/src/main/org/h2/command/CommandContainer.java b/h2/src/main/org/h2/command/CommandContainer.java
index 27a561bd18..30fcf5bc53 100644
--- a/h2/src/main/org/h2/command/CommandContainer.java
+++ b/h2/src/main/org/h2/command/CommandContainer.java
@@ -136,9 +136,10 @@ private void recompileIfRequired() {
// TODO test with 'always recompile'
prepared.setModificationMetaId(0);
String sql = prepared.getSQL();
+ ArrayList tokens = prepared.getSQLTokens();
ArrayList oldParams = prepared.getParameters();
Parser parser = new Parser(session);
- prepared = parser.parse(sql);
+ prepared = parser.parse(sql, tokens);
long mod = prepared.getModificationMetaId();
prepared.setModificationMetaId(0);
ArrayList newParams = prepared.getParameters();
diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java
index 5f4d02d08a..f86cbb59cb 100644
--- a/h2/src/main/org/h2/command/Parser.java
+++ b/h2/src/main/org/h2/command/Parser.java
@@ -8,6 +8,38 @@
*/
package org.h2.command;
+import static org.h2.command.Token.ASTERISK;
+import static org.h2.command.Token.AT;
+import static org.h2.command.Token.BIGGER;
+import static org.h2.command.Token.BIGGER_EQUAL;
+import static org.h2.command.Token.CLOSE_BRACE;
+import static org.h2.command.Token.CLOSE_BRACKET;
+import static org.h2.command.Token.CLOSE_PAREN;
+import static org.h2.command.Token.COLON;
+import static org.h2.command.Token.COLON_COLON;
+import static org.h2.command.Token.COLON_EQ;
+import static org.h2.command.Token.COMMA;
+import static org.h2.command.Token.CONCATENATION;
+import static org.h2.command.Token.DOT;
+import static org.h2.command.Token.END_OF_INPUT;
+import static org.h2.command.Token.EQUAL;
+import static org.h2.command.Token.LITERAL;
+import static org.h2.command.Token.MINUS_SIGN;
+import static org.h2.command.Token.NOT_EQUAL;
+import static org.h2.command.Token.NOT_TILDE;
+import static org.h2.command.Token.OPEN_BRACE;
+import static org.h2.command.Token.OPEN_BRACKET;
+import static org.h2.command.Token.OPEN_PAREN;
+import static org.h2.command.Token.PARAMETER;
+import static org.h2.command.Token.PERCENT;
+import static org.h2.command.Token.PLUS_SIGN;
+import static org.h2.command.Token.SEMICOLON;
+import static org.h2.command.Token.SLASH;
+import static org.h2.command.Token.SMALLER;
+import static org.h2.command.Token.SMALLER_EQUAL;
+import static org.h2.command.Token.SPATIAL_INTERSECTS;
+import static org.h2.command.Token.TILDE;
+import static org.h2.command.Token.TOKENS;
import static org.h2.util.ParserUtil.ALL;
import static org.h2.util.ParserUtil.AND;
import static org.h2.util.ParserUtil.ANY;
@@ -100,9 +132,6 @@
import static org.h2.util.ParserUtil.YEAR;
import static org.h2.util.ParserUtil._ROWID_;
-import java.io.ByteArrayOutputStream;
-import java.math.BigDecimal;
-import java.math.BigInteger;
import java.nio.charset.Charset;
import java.text.Collator;
import java.util.ArrayList;
@@ -118,9 +147,9 @@
import org.h2.api.ErrorCode;
import org.h2.api.IntervalQualifier;
import org.h2.api.Trigger;
-import org.h2.command.ddl.AlterDomainExpressions;
import org.h2.command.ddl.AlterDomainAddConstraint;
import org.h2.command.ddl.AlterDomainDropConstraint;
+import org.h2.command.ddl.AlterDomainExpressions;
import org.h2.command.ddl.AlterDomainRename;
import org.h2.command.ddl.AlterDomainRenameConstraint;
import org.h2.command.ddl.AlterIndexRename;
@@ -360,7 +389,6 @@
import org.h2.value.ValueArray;
import org.h2.value.ValueBigint;
import org.h2.value.ValueDate;
-import org.h2.value.ValueDecfloat;
import org.h2.value.ValueDouble;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueInteger;
@@ -374,7 +402,6 @@
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
import org.h2.value.ValueUuid;
-import org.h2.value.ValueVarbinary;
import org.h2.value.ValueVarchar;
/**
@@ -390,425 +417,6 @@ public class Parser {
"WITH statement supports only SELECT, TABLE, VALUES, " +
"CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements";
- // used during the tokenizer phase
- private static final int CHAR_END = 1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
- private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5,
- CHAR_SPECIAL_2 = 6;
- private static final int CHAR_STRING = 7, CHAR_DOT = 8,
- CHAR_DOLLAR_QUOTED_STRING = 9;
-
- // these are token types, see also types in ParserUtil
-
- /**
- * Token with parameter.
- */
- private static final int PARAMETER = LAST_KEYWORD + 1;
-
- /**
- * End of input.
- */
- private static final int END_OF_INPUT = PARAMETER + 1;
-
- /**
- * Token with literal.
- */
- private static final int LITERAL = END_OF_INPUT + 1;
-
- /**
- * The token "=".
- */
- private static final int EQUAL = LITERAL + 1;
-
- /**
- * The token ">=".
- */
- private static final int BIGGER_EQUAL = EQUAL + 1;
-
- /**
- * The token ">".
- */
- private static final int BIGGER = BIGGER_EQUAL + 1;
-
- /**
- * The token "<".
- */
- private static final int SMALLER = BIGGER + 1;
-
- /**
- * The token "<=".
- */
- private static final int SMALLER_EQUAL = SMALLER + 1;
-
- /**
- * The token "<>" or "!=".
- */
- private static final int NOT_EQUAL = SMALLER_EQUAL + 1;
-
- /**
- * The token "@".
- */
- private static final int AT = NOT_EQUAL + 1;
-
- /**
- * The token "-".
- */
- private static final int MINUS_SIGN = AT + 1;
-
- /**
- * The token "+".
- */
- private static final int PLUS_SIGN = MINUS_SIGN + 1;
-
- /**
- * The token "||".
- */
- private static final int CONCATENATION = PLUS_SIGN + 1;
-
- /**
- * The token "(".
- */
- private static final int OPEN_PAREN = CONCATENATION + 1;
-
- /**
- * The token ")".
- */
- private static final int CLOSE_PAREN = OPEN_PAREN + 1;
-
- /**
- * The token &.
- */
- private static final int AMPERSAND = CLOSE_PAREN + 1;
-
- /**
- * The token "&&".
- */
- private static final int SPATIAL_INTERSECTS = AMPERSAND + 1;
-
- /**
- * The token "*".
- */
- private static final int ASTERISK = SPATIAL_INTERSECTS + 1;
-
- /**
- * The token ",".
- */
- private static final int COMMA = ASTERISK + 1;
-
- /**
- * The token ".".
- */
- private static final int DOT = COMMA + 1;
-
- /**
- * The token "{".
- */
- private static final int OPEN_BRACE = DOT + 1;
-
- /**
- * The token "}".
- */
- private static final int CLOSE_BRACE = OPEN_BRACE + 1;
-
- /**
- * The token "/".
- */
- private static final int SLASH = CLOSE_BRACE + 1;
-
- /**
- * The token "%".
- */
- private static final int PERCENT = SLASH + 1;
-
- /**
- * The token ";".
- */
- private static final int SEMICOLON = PERCENT + 1;
-
- /**
- * The token ":".
- */
- private static final int COLON = SEMICOLON + 1;
-
- /**
- * The token "[".
- */
- private static final int OPEN_BRACKET = COLON + 1;
-
- /**
- * The token "]".
- */
- private static final int CLOSE_BRACKET = OPEN_BRACKET + 1;
-
- /**
- * The token "~".
- */
- private static final int TILDE = CLOSE_BRACKET + 1;
-
- /**
- * The token "::".
- */
- private static final int COLON_COLON = TILDE + 1;
-
- /**
- * The token ":=".
- */
- private static final int COLON_EQ = COLON_COLON + 1;
-
- /**
- * The token "!~".
- */
- private static final int NOT_TILDE = COLON_EQ + 1;
-
- private static final String[] TOKENS = {
- // Unused
- null,
- // KEYWORD
- null,
- // IDENTIFIER
- null,
- // ALL
- "ALL",
- // AND
- "AND",
- // ANY
- "ANY",
- // ARRAY
- "ARRAY",
- // AS
- "AS",
- // ASYMMETRIC
- "ASYMMETRIC",
- // AUTHORIZATION
- "AUTHORIZATION",
- // BETWEEN
- "BETWEEN",
- // CASE
- "CASE",
- // CAST
- "CAST",
- // CHECK
- "CHECK",
- // CONSTRAINT
- "CONSTRAINT",
- // CROSS
- "CROSS",
- // CURRENT_CATALOG
- "CURRENT_CATALOG",
- // CURRENT_DATE
- "CURRENT_DATE",
- // CURRENT_PATH
- "CURRENT_PATH",
- // CURRENT_ROLE
- "CURRENT_ROLE",
- // CURRENT_SCHEMA
- "CURRENT_SCHEMA",
- // CURRENT_TIME
- "CURRENT_TIME",
- // CURRENT_TIMESTAMP
- "CURRENT_TIMESTAMP",
- // CURRENT_USER
- "CURRENT_USER",
- // DAY
- "DAY",
- // DEFAULT
- "DEFAULT",
- // DISTINCT
- "DISTINCT",
- // ELSE
- "ELSE",
- // END
- "END",
- // EXCEPT
- "EXCEPT",
- // EXISTS
- "EXISTS",
- // FALSE
- "FALSE",
- // FETCH
- "FETCH",
- // FOR
- "FOR",
- // FOREIGN
- "FOREIGN",
- // FROM
- "FROM",
- // FULL
- "FULL",
- // GROUP
- "GROUP",
- // HAVING
- "HAVING",
- // HOUR
- "HOUR",
- // IF
- "IF",
- // IN
- "IN",
- // INNER
- "INNER",
- // INTERSECT
- "INTERSECT",
- // INTERVAL
- "INTERVAL",
- // IS
- "IS",
- // JOIN
- "JOIN",
- // KEY
- "KEY",
- // LEFT
- "LEFT",
- // LIKE
- "LIKE",
- // LIMIT
- "LIMIT",
- // LOCALTIME
- "LOCALTIME",
- // LOCALTIMESTAMP
- "LOCALTIMESTAMP",
- // MINUS
- "MINUS",
- // MINUTE
- "MINUTE",
- // MONTH
- "MONTH",
- // NATURAL
- "NATURAL",
- // NOT
- "NOT",
- // NULL
- "NULL",
- // OFFSET
- "OFFSET",
- // ON
- "ON",
- // OR
- "OR",
- // ORDER
- "ORDER",
- // PRIMARY
- "PRIMARY",
- // QUALIFY
- "QUALIFY",
- // RIGHT
- "RIGHT",
- // ROW
- "ROW",
- // ROWNUM
- "ROWNUM",
- // SECOND
- "SECOND",
- // SELECT
- "SELECT",
- // SESSION_USER
- "SESSION_USER",
- // SET
- "SET",
- // SOME
- "SOME",
- // SYMMETRIC
- "SYMMETRIC",
- // SYSTEM_USER
- "SYSTEM_USER",
- // TABLE
- "TABLE",
- // TO
- "TO",
- // TRUE
- "TRUE",
- // UESCAPE
- "UESCAPE",
- // UNION
- "UNION",
- // UNIQUE
- "UNIQUE",
- // UNKNOWN
- "UNKNOWN",
- // USER
- "USER",
- // USING
- "USING",
- // VALUE
- "VALUE",
- // VALUES
- "VALUES",
- // WHEN
- "WHEN",
- // WHERE
- "WHERE",
- // WINDOW
- "WINDOW",
- // WITH
- "WITH",
- // YEAR
- "YEAR",
- // _ROWID_
- "_ROWID_",
- // PARAMETER
- "?",
- // END
- null,
- // VALUE
- null,
- // EQUAL
- "=",
- // BIGGER_EQUAL
- ">=",
- // BIGGER
- ">",
- // SMALLER
- "<",
- // SMALLER_EQUAL
- "<=",
- // NOT_EQUAL
- "<>",
- // AT
- "@",
- // MINUS_SIGN
- "-",
- // PLUS_SIGN
- "+",
- // STRING_CONCAT
- "||",
- // OPEN_PAREN
- "(",
- // CLOSE_PAREN
- ")",
- // SPATIAL_INTERSECTS
- "&&",
- // ASTERISK
- "*",
- // COMMA
- ",",
- // DOT
- ".",
- // OPEN_BRACE
- "{",
- // CLOSE_BRACE
- "}",
- // SLASH
- "/",
- // PERCENT
- "%",
- // SEMICOLON
- ";",
- // COLON
- ":",
- // OPEN_BRACKET
- "[",
- // CLOSE_BRACKET
- "]",
- // TILDE
- "~",
- // COLON_COLON
- "::",
- // COLON_EQ
- ":=",
- // NOT_TILDE
- "!~",
- // End
- };
-
private final Database database;
private final SessionLocal session;
@@ -828,21 +436,12 @@ public class Parser {
private final BitSet nonKeywords;
- /** indicates character-type for each char in sqlCommand */
- private int[] characterTypes;
+ ArrayList tokens;
+ int tokenIndex;
+ Token token;
private int currentTokenType;
private String currentToken;
- private boolean currentTokenQuoted;
- private Value currentValue;
- private String originalSQL;
- /** copy of originalSQL, with comments blanked out */
private String sqlCommand;
- /** cached array if chars from sqlCommand */
- private char[] sqlCommandChars;
- /** index into sqlCommand of previous token */
- private int lastParseIndex;
- /** index into sqlCommand of current token */
- private int parseIndex;
private CreateView createView;
private Prepared currentPrepared;
private Select currentSelect;
@@ -935,7 +534,7 @@ public Parser() {
* @return the prepared object
*/
public Prepared prepare(String sql) {
- Prepared p = parse(sql);
+ Prepared p = parse(sql, null);
p.prepare();
if (currentTokenType != END_OF_INPUT) {
throw getSyntaxError();
@@ -951,7 +550,7 @@ public Prepared prepare(String sql) {
*/
public Command prepareCommand(String sql) {
try {
- Prepared p = parse(sql);
+ Prepared p = parse(sql, null);
if (currentTokenType != SEMICOLON && currentTokenType != END_OF_INPUT) {
addExpected(SEMICOLON);
throw getSyntaxError();
@@ -962,50 +561,59 @@ public Command prepareCommand(String sql) {
CommandContainer.clearCTE(session, p);
throw t;
}
- if (parseIndex < sql.length()) {
- sql = sql.substring(0, parseIndex);
+ int sqlIndex = token.start();
+ if (sqlIndex < sql.length()) {
+ sql = sql.substring(0, sqlIndex);
}
CommandContainer c = new CommandContainer(session, sql, p);
- if (currentTokenType == SEMICOLON) {
- String remaining = originalSQL.substring(parseIndex);
- if (!StringUtils.isWhitespaceOrEmpty(remaining)) {
- return prepareCommandList(c, p, sql, remaining);
- }
+ while (currentTokenType == SEMICOLON) {
+ read();
+ }
+ if (currentTokenType != END_OF_INPUT) {
+ int offset = token.start();
+ return prepareCommandList(c, p, sql, sqlCommand.substring(offset), getRemainingTokens(offset));
}
return c;
} catch (DbException e) {
- throw e.addSQL(originalSQL);
+ throw e.addSQL(sqlCommand);
}
}
- private CommandList prepareCommandList(CommandContainer command, Prepared p, String sql, String remaining) {
+ private CommandList prepareCommandList(CommandContainer command, Prepared p, String sql, String remainingSql,
+ ArrayList remainingTokens) {
try {
ArrayList list = Utils.newSmallArrayList();
- do {
+ for (;;) {
if (p instanceof DefineCommand) {
// Next commands may depend on results of this command.
- return new CommandList(session, sql, command, list, parameters, remaining);
+ return new CommandList(session, sql, command, list, parameters, remainingSql);
}
suppliedParameters = parameters;
suppliedParameterList = indexedParameterList;
try {
- p = parse(remaining);
+ p = parse(remainingSql, remainingTokens);
} catch (DbException ex) {
// This command may depend on results of previous commands.
if (ex.getErrorCode() == ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS) {
throw ex;
}
- return new CommandList(session, sql, command, list, parameters, remaining);
+ return new CommandList(session, sql, command, list, parameters, remainingSql);
}
list.add(p);
- if (currentTokenType == END_OF_INPUT) {
- break;
- }
- if (currentTokenType != SEMICOLON) {
+ if (currentTokenType != SEMICOLON && currentTokenType != END_OF_INPUT) {
addExpected(SEMICOLON);
throw getSyntaxError();
}
- } while (!StringUtils.isWhitespaceOrEmpty(remaining = originalSQL.substring(parseIndex)));
+ while (currentTokenType == SEMICOLON) {
+ read();
+ }
+ if (currentTokenType == END_OF_INPUT) {
+ break;
+ }
+ int offset = token.start();
+ remainingSql = sqlCommand.substring(offset);
+ remainingTokens = getRemainingTokens(offset);
+ }
return new CommandList(session, sql, command, list, parameters, null);
} catch (Throwable t) {
command.clearCTE();
@@ -1013,13 +621,26 @@ private CommandList prepareCommandList(CommandContainer command, Prepared p, Str
}
}
+ private ArrayList getRemainingTokens(int offset) {
+ List subList = tokens.subList(tokenIndex, tokens.size());
+ ArrayList remainingTokens = new ArrayList<>(subList);
+ subList.clear();
+ tokens.add(new Token.EndOfInputToken(offset));
+ for (Token token : remainingTokens) {
+ token.subtractFromStart(offset);
+ }
+ return remainingTokens;
+ }
+
/**
* Parse the statement, but don't prepare it for execution.
*
* @param sql the SQL statement to parse
+ * @param tokens tokens, or null
* @return the prepared object
*/
- Prepared parse(String sql) {
+ Prepared parse(String sql, ArrayList tokens) {
+ initialize(sql, tokens, false);
Prepared p;
try {
// first, try the fast variant
@@ -1027,6 +648,7 @@ Prepared parse(String sql) {
} catch (DbException e) {
if (e.getErrorCode() == ErrorCode.SYNTAX_ERROR_1) {
// now, get the detailed exception
+ resetTokenIndex();
p = parse(sql, true);
} else {
throw e.addSQL(sql);
@@ -1038,7 +660,6 @@ Prepared parse(String sql) {
}
private Prepared parse(String sql, boolean withExpectedList) {
- initialize(sql);
if (withExpectedList) {
expectedList = new ArrayList<>();
} else {
@@ -1066,7 +687,7 @@ private Prepared parse(String sql, boolean withExpectedList) {
}
private Prepared parsePrepared() {
- int start = lastParseIndex;
+ int start = tokenIndex;
Prepared c = null;
switch (currentTokenType) {
case END_OF_INPUT:
@@ -1079,6 +700,7 @@ private Prepared parsePrepared() {
// this is an 'out' parameter - set a dummy value
readParameter().setValue(ValueNull.INSTANCE);
read(EQUAL);
+ start = tokenIndex;
read("CALL");
c = parseCall();
break;
@@ -1097,7 +719,7 @@ private Prepared parsePrepared() {
c = parseSet();
break;
case IDENTIFIER:
- if (currentTokenQuoted) {
+ if (token.isQuoted()) {
break;
}
/*
@@ -1269,9 +891,9 @@ private Prepared parsePrepared() {
private DbException getSyntaxError() {
if (expectedList == null || expectedList.isEmpty()) {
- return DbException.getSyntaxError(sqlCommand, parseIndex);
+ return DbException.getSyntaxError(sqlCommand, token.start());
}
- return DbException.getSyntaxError(sqlCommand, parseIndex, String.join(", ", expectedList));
+ return DbException.getSyntaxError(sqlCommand, token.start(), String.join(", ", expectedList));
}
private Prepared parseBackup() {
@@ -1776,7 +1398,7 @@ private static Prepared prepare(SessionLocal s, String sql,
}
private boolean isDerivedTable() {
- int start = lastParseIndex;
+ int start = tokenIndex;
int level = 0;
while (readIf(OPEN_PAREN)) {
level++;
@@ -1815,12 +1437,12 @@ private boolean isDerivedTable() {
}
}
}
- reread(start);
+ setTokenIndex(start);
return query;
}
private boolean isQuery() {
- int start = lastParseIndex;
+ int start = tokenIndex;
int level = 0;
while (readIf(OPEN_PAREN)) {
level++;
@@ -1851,7 +1473,7 @@ private boolean isQuery() {
}
} while (--level > 0);
}
- reread(start);
+ setTokenIndex(start);
return query;
}
@@ -1875,13 +1497,13 @@ private boolean scanToCloseParen() {
}
private boolean isQueryQuick() {
- int start = lastParseIndex;
+ int start = tokenIndex;
while (readIf(OPEN_PAREN)) {
// need to read ahead, it could be a nested union:
// ((select 1) union (select 1))
}
boolean query = isDirectQuery();
- reread(start);
+ setTokenIndex(start);
return query;
}
@@ -2201,9 +1823,9 @@ private TableFilter readTablePrimary() {
ArrayTableFunction function = readTableFunction(ArrayTableFunction.TABLE);
table = new FunctionTable(database.getMainSchema(), session, function);
} else {
- boolean quoted = currentTokenQuoted;
+ boolean quoted = token.isQuoted();
String tableName = readIdentifier();
- int backupIndex = parseIndex;
+ int backupIndex = tokenIndex;
schemaName = null;
if (readIf(DOT)) {
tableName = readIdentifierWithSchema2(tableName);
@@ -2335,7 +1957,7 @@ private TableFilter buildTableFilter(Table table, String alias, ArrayList commandTokens;
+ if (start == 0 && currentTokenType == END_OF_INPUT) {
+ commandTokens = tokens;
+ if (beginIndex != 0) {
+ for (int i = 0, l = commandTokens.size() - 1; i < l; i++) {
+ commandTokens.get(i).subtractFromStart(beginIndex);
+ }
+ }
+ token.setStart(s.length());
+ sqlCommand = s;
+ } else {
+ List subList = tokens.subList(start, tokenIndex);
+ commandTokens = new ArrayList<>(subList.size() + 1);
+ for (int i = start; i < tokenIndex; i++) {
+ Token t = tokens.get(i).clone();
+ t.subtractFromStart(beginIndex);
+ commandTokens.add(t);
+ }
+ commandTokens.add(new Token.EndOfInputToken(s.length()));
+ }
+ command.setSQL(s, commandTokens);
}
private Expression readExpressionOrDefault() {
@@ -3509,7 +3161,7 @@ private Expression readCondition() {
return new UniquePredicate(query);
}
default:
- int index = lastParseIndex;
+ int index = tokenIndex;
if (readIf("INTERSECTS")) {
if (readIf(OPEN_PAREN)) {
Expression r1 = readConcat();
@@ -3518,7 +3170,7 @@ private Expression readCondition() {
read(CLOSE_PAREN);
return new Comparison(Comparison.SPATIAL_INTERSECTS, r1, r2, false);
} else {
- reread(index);
+ setTokenIndex(index);
}
}
if (expectedList != null) {
@@ -3531,13 +3183,11 @@ private Expression readCondition() {
l = c;
// special case: NOT NULL is not part of an expression (as in CREATE
// TABLE TEST(ID INT DEFAULT 0 NOT NULL))
- int backup = parseIndex;
+ int backup = tokenIndex;
boolean not = readIf(NOT);
if (not && isToken(NULL)) {
// this really only works for NOT NULL!
- parseIndex = backup;
- currentToken = "NOT";
- currentTokenType = NOT;
+ setTokenIndex(backup);
break;
}
c = readConditionRightHandSide(l, not, false);
@@ -3711,7 +3361,7 @@ private Expression readLikePredicate(Expression left, LikeType likeType, boolean
}
private Expression readComparison(Expression left, int compareType, boolean whenOperand) {
- int start = lastParseIndex;
+ int start = tokenIndex;
if (readIf(ALL)) {
read(OPEN_PAREN);
if (isQuery()) {
@@ -3719,7 +3369,7 @@ private Expression readComparison(Expression left, int compareType, boolean when
left = new ConditionInQuery(left, false, whenOperand, query, true, compareType);
read(CLOSE_PAREN);
} else {
- reread(start);
+ setTokenIndex(start);
left = new Comparison(compareType, left, readConcat(), whenOperand);
}
} else if (readIf(ANY) || readIf(SOME)) {
@@ -3733,7 +3383,7 @@ private Expression readComparison(Expression left, int compareType, boolean when
left = new ConditionInQuery(left, false, whenOperand, query, false, compareType);
read(CLOSE_PAREN);
} else {
- reread(start);
+ setTokenIndex(start);
left = new Comparison(compareType, left, readConcat(), whenOperand);
}
} else {
@@ -3815,6 +3465,7 @@ private Expression readTildeCondition(Expression r, boolean not) {
private Expression readAggregate(AggregateType aggregateType, String aggregateName) {
if (currentSelect == null) {
+ expectedList = null;
throw getSyntaxError();
}
Aggregate r;
@@ -3889,12 +3540,12 @@ private Expression readAggregate(AggregateType aggregateType, String aggregateNa
orderByList = null;
}
Expression[] args = new Expression[] { arg };
- int index = lastParseIndex;
+ int index = tokenIndex;
read(CLOSE_PAREN);
if (orderByList == null && isToken("WITHIN")) {
r = readWithinGroup(aggregateType, args, distinct, extraArguments, false, false);
} else {
- reread(index);
+ setTokenIndex(index);
r = new Aggregate(AggregateType.LISTAGG, args, currentSelect, distinct);
r.setExtraArguments(extraArguments);
if (orderByList != null) {
@@ -3942,7 +3593,7 @@ private Expression readAggregate(AggregateType aggregateType, String aggregateNa
String sql = expr.getSQL(HasSQL.DEFAULT_SQL_FLAGS), sql2 = expr2.getSQL(HasSQL.DEFAULT_SQL_FLAGS);
if (!sql.equals(sql2)) {
throw DbException.getSyntaxError(ErrorCode.IDENTICAL_EXPRESSIONS_SHOULD_BE_USED, sqlCommand,
- lastParseIndex, sql, sql2);
+ token.start(), sql, sql2);
}
readAggregateOrder(r, expr, true);
} else {
@@ -4109,13 +3760,13 @@ private Window readWindowSpecification() {
read(OPEN_PAREN);
String parent = null;
if (currentTokenType == IDENTIFIER) {
- String token = currentToken;
- if (currentTokenQuoted || ( //
- !equalsToken(token, "PARTITION") //
- && !equalsToken(token, "ROWS") //
- && !equalsToken(token, "RANGE") //
- && !equalsToken(token, "GROUPS"))) {
- parent = token;
+ String current = currentToken;
+ if (token.isQuoted() || ( //
+ !equalsToken(current, "PARTITION") //
+ && !equalsToken(current, "ROWS") //
+ && !equalsToken(current, "RANGE") //
+ && !equalsToken(current, "GROUPS"))) {
+ parent = current;
read();
}
}
@@ -4154,7 +3805,7 @@ private WindowFrame readWindowFrame() {
starting = readWindowFrameStarting();
following = null;
}
- int idx = lastParseIndex;
+ int sqlIndex = token.start();
WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS;
if (readIf("EXCLUDE")) {
if (readIf("CURRENT")) {
@@ -4171,7 +3822,7 @@ private WindowFrame readWindowFrame() {
}
WindowFrame frame = new WindowFrame(units, starting, following, exclusion);
if (!frame.isValid()) {
- throw DbException.getSyntaxError(sqlCommand, idx);
+ throw DbException.getSyntaxError(sqlCommand, sqlIndex);
}
return frame;
}
@@ -4989,13 +4640,13 @@ private int readDateTimeField() {
int field = -1;
switch (currentTokenType) {
case IDENTIFIER:
- if (!currentTokenQuoted) {
+ if (!token.isQuoted()) {
field = DateTimeFunction.getField(currentToken);
}
break;
case LITERAL:
- if (currentValue.getValueType() == Value.VARCHAR) {
- field = DateTimeFunction.getField(currentValue.getString());
+ if (token.value(session).getValueType() == Value.VARCHAR) {
+ field = DateTimeFunction.getField(token.value(session).getString());
}
break;
case YEAR:
@@ -5098,7 +4749,7 @@ private void readRespectOrIgnoreNulls(WindowFunction function) {
}
private boolean readJsonObjectFunctionFlags(ExpressionWithFlags function, boolean forArray) {
- int start = lastParseIndex;
+ int start = tokenIndex;
boolean result = false;
int flags = function.getFlags();
if (readIf(NULL)) {
@@ -5107,7 +4758,7 @@ private boolean readJsonObjectFunctionFlags(ExpressionWithFlags function, boolea
flags &= ~JsonConstructorUtils.JSON_ABSENT_ON_NULL;
result = true;
} else {
- reread(start);
+ setTokenIndex(start);
return false;
}
} else if (readIf("ABSENT")) {
@@ -5116,7 +4767,7 @@ private boolean readJsonObjectFunctionFlags(ExpressionWithFlags function, boolea
flags |= JsonConstructorUtils.JSON_ABSENT_ON_NULL;
result = true;
} else {
- reread(start);
+ setTokenIndex(start);
return false;
}
}
@@ -5134,7 +4785,7 @@ private boolean readJsonObjectFunctionFlags(ExpressionWithFlags function, boolea
} else if (result) {
throw getSyntaxError();
} else {
- reread(start);
+ setTokenIndex(start);
return false;
}
}
@@ -5286,11 +4937,10 @@ private void checkDatabaseName(String databaseName) {
private Parameter readParameter() {
// there must be no space between ? and the number
- boolean indexed = Character.isDigit(sqlCommandChars[parseIndex]);
-
+ int index = ((Token.ParameterToken) token).index();
+ read();
Parameter p;
- if (indexed) {
- readParameterIndex();
+ if (index > 0) {
if (indexedParameterList == null) {
if (parameters == null) {
// this can occur when parsing expressions only (for
@@ -5302,11 +4952,11 @@ private Parameter readParameter() {
}
indexedParameterList = Utils.newSmallArrayList();
}
- int index = currentValue.getInt() - 1;
- if (index < 0 || index >= Constants.MAX_PARAMETER_INDEX) {
+ if (index > Constants.MAX_PARAMETER_INDEX) {
throw DbException.getInvalidValueException(
- "parameter index", index + 1);
+ "parameter index", index);
}
+ index--;
if (indexedParameterList.size() <= index) {
indexedParameterList.ensureCapacity(index + 1);
while (indexedParameterList.size() <= index) {
@@ -5319,9 +4969,7 @@ private Parameter readParameter() {
indexedParameterList.set(index, p);
parameters.add(p);
}
- read();
} else {
- read();
if (indexedParameterList != null) {
throw DbException
.get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS);
@@ -5353,7 +5001,7 @@ private Expression readTerm() {
case MINUS_SIGN:
read();
if (currentTokenType == LITERAL) {
- r = ValueExpression.get(currentValue.negate());
+ r = ValueExpression.get(token.value(session).negate());
int rType = r.getType().getValueType();
if (rType == Value.BIGINT &&
r.getValue(session).getLong() == Integer.MIN_VALUE) {
@@ -5474,7 +5122,7 @@ private Expression readTerm() {
r = new ExpressionColumn(database, null, null);
break;
case LITERAL:
- r = ValueExpression.get(currentValue);
+ r = ValueExpression.get(token.value(session));
read();
break;
case VALUES:
@@ -5586,7 +5234,7 @@ private Expression readTerm() {
//$FALL-THROUGH$
case IDENTIFIER:
String name = currentToken;
- boolean quoted = currentTokenQuoted;
+ boolean quoted = token.isQuoted();
read();
if (readIf(OPEN_PAREN)) {
r = readFunction(null, name);
@@ -5622,7 +5270,7 @@ private Expression readTerm() {
if (ti != null) {
r = new CastSpecification(r, ti);
}
- int index = lastParseIndex;
+ int index = tokenIndex;
if (readIf("AT")) {
if (readIf("TIME")) {
read("ZONE");
@@ -5632,14 +5280,14 @@ private Expression readTerm() {
r = new TimeZoneOperation(r, null);
continue;
} else {
- reread(index);
+ setTokenIndex(index);
}
} else if (readIf("FORMAT")) {
if (readIf("JSON")) {
r = new Format(r, FormatEnum.JSON);
continue;
} else {
- reread(index);
+ setTokenIndex(index);
}
}
break;
@@ -5698,28 +5346,28 @@ private Expression readTermWithIdentifier(String name, boolean quoted) {
switch (name.charAt(0) & 0xffdf) {
case 'C':
if (equalsToken("CURRENT", name)) {
- int index = lastParseIndex;
+ int index = tokenIndex;
if (readIf(VALUE) && readIf(FOR)) {
return new SequenceValue(readSequence());
}
- reread(index);
+ setTokenIndex(index);
if (database.getMode().getEnum() == ModeEnum.DB2) {
return parseDB2SpecialRegisters(name);
}
}
break;
case 'D':
- if (currentTokenType == LITERAL && currentValue.getValueType() == Value.VARCHAR &&
+ if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR &&
(equalsToken("DATE", name) || equalsToken("D", name))) {
- String date = currentValue.getString();
+ String date = token.value(session).getString();
read();
return ValueExpression.get(ValueDate.parse(date));
}
break;
case 'E':
- if (currentTokenType == LITERAL && currentValue.getValueType() == Value.VARCHAR //
+ if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR //
&& equalsToken("E", name)) {
- String text = currentValue.getString();
+ String text = token.value(session).getString();
// the PostgreSQL ODBC driver uses
// LIKE E'PROJECT\\_DATA' instead of LIKE
// 'PROJECT\_DATA'
@@ -5731,13 +5379,13 @@ && equalsToken("E", name)) {
break;
case 'G':
if (currentTokenType == LITERAL) {
- int t = currentValue.getValueType();
+ int t = token.value(session).getValueType();
if (t == Value.VARCHAR && equalsToken("GEOMETRY", name)) {
- ValueExpression v = ValueExpression.get(ValueGeometry.get(currentValue.getString()));
+ ValueExpression v = ValueExpression.get(ValueGeometry.get(token.value(session).getString()));
read();
return v;
} else if (t == Value.VARBINARY && equalsToken("GEOMETRY", name)) {
- ValueExpression v = ValueExpression.get(ValueGeometry.getFromEWKB(currentValue.getBytesNoCopy()));
+ ValueExpression v = ValueExpression.get(ValueGeometry.getFromEWKB(token.value(session).getBytesNoCopy()));
read();
return v;
}
@@ -5745,13 +5393,13 @@ && equalsToken("E", name)) {
break;
case 'J':
if (currentTokenType == LITERAL) {
- int t = currentValue.getValueType();
+ int t = token.value(session).getValueType();
if (t == Value.VARCHAR && equalsToken("JSON", name)) {
- ValueExpression v = ValueExpression.get(ValueJson.fromJson(currentValue.getString()));
+ ValueExpression v = ValueExpression.get(ValueJson.fromJson(token.value(session).getString()));
read();
return v;
} else if (t == Value.VARBINARY && equalsToken("JSON", name)) {
- ValueExpression v = ValueExpression.get(ValueJson.fromJson(currentValue.getBytesNoCopy()));
+ ValueExpression v = ValueExpression.get(ValueJson.fromJson(token.value(session).getBytesNoCopy()));
read();
return v;
}
@@ -5759,11 +5407,11 @@ && equalsToken("E", name)) {
break;
case 'N':
if (equalsToken("NEXT", name)) {
- int index = lastParseIndex;
+ int index = tokenIndex;
if (readIf(VALUE) && readIf(FOR)) {
return new SequenceValue(readSequence(), getCurrentSelectOrPrepared());
}
- reread(index);
+ setTokenIndex(index);
}
break;
case 'T':
@@ -5771,10 +5419,10 @@ && equalsToken("E", name)) {
if (readIf(WITH)) {
read("TIME");
read("ZONE");
- if (currentTokenType != LITERAL || currentValue.getValueType() != Value.VARCHAR) {
+ if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) {
throw getSyntaxError();
}
- String time = currentValue.getString();
+ String time = token.value(session).getString();
read();
return ValueExpression.get(ValueTimeTimeZone.parse(time));
} else {
@@ -5783,8 +5431,8 @@ && equalsToken("E", name)) {
read("TIME");
read("ZONE");
}
- if (currentTokenType == LITERAL && currentValue.getValueType() == Value.VARCHAR) {
- String time = currentValue.getString();
+ if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) {
+ String time = token.value(session).getString();
read();
return ValueExpression.get(ValueTime.parse(time));
} else if (without) {
@@ -5795,10 +5443,10 @@ && equalsToken("E", name)) {
if (readIf(WITH)) {
read("TIME");
read("ZONE");
- if (currentTokenType != LITERAL || currentValue.getValueType() != Value.VARCHAR) {
+ if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) {
throw getSyntaxError();
}
- String timestamp = currentValue.getString();
+ String timestamp = token.value(session).getString();
read();
return ValueExpression.get(ValueTimestampTimeZone.parse(timestamp, session));
} else {
@@ -5807,30 +5455,30 @@ && equalsToken("E", name)) {
read("TIME");
read("ZONE");
}
- if (currentTokenType == LITERAL && currentValue.getValueType() == Value.VARCHAR) {
- String timestamp = currentValue.getString();
+ if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) {
+ String timestamp = token.value(session).getString();
read();
return ValueExpression.get(ValueTimestamp.parse(timestamp, session));
} else if (without) {
throw getSyntaxError();
}
}
- } else if (currentTokenType == LITERAL && currentValue.getValueType() == Value.VARCHAR) {
+ } else if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) {
if (equalsToken("T", name)) {
- String time = currentValue.getString();
+ String time = token.value(session).getString();
read();
return ValueExpression.get(ValueTime.parse(time));
} else if (equalsToken("TS", name)) {
- String timestamp = currentValue.getString();
+ String timestamp = token.value(session).getString();
read();
return ValueExpression.get(ValueTimestamp.parse(timestamp, session));
}
}
break;
case 'U':
- if (currentTokenType == LITERAL && currentValue.getValueType() == Value.VARCHAR
+ if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR
&& (equalsToken("UUID", name))) {
- String uuid = currentValue.getString();
+ String uuid = token.value(session).getString();
read();
return ValueExpression.get(ValueUuid.get(uuid));
}
@@ -5848,11 +5496,11 @@ private Expression readInterval() {
if (!negative) {
readIf(PLUS_SIGN);
}
- if (currentTokenType != LITERAL || currentValue.getValueType() != Value.VARCHAR) {
+ if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) {
addExpected("string");
throw getSyntaxError();
}
- String s = currentValue.getString();
+ String s = token.value(session).getString();
read();
IntervalQualifier qualifier;
switch (currentTokenType) {
@@ -5998,14 +5646,12 @@ private SimpleCase.SimpleWhen readSimpleWhenClause(Expression caseOperand) {
}
private Expression readWhenOperand(Expression caseOperand) {
- int backup = parseIndex;
+ int backup = tokenIndex;
boolean not = readIf(NOT);
Expression whenOperand = readConditionRightHandSide(caseOperand, not, true);
if (whenOperand == null) {
if (not) {
- parseIndex = backup;
- currentToken = "NOT";
- currentTokenType = NOT;
+ setTokenIndex(backup);
}
whenOperand = readExpression();
}
@@ -6029,13 +5675,14 @@ private int readInt() {
read();
}
if (currentTokenType != LITERAL) {
- throw DbException.getSyntaxError(sqlCommand, parseIndex, "integer");
+ throw DbException.getSyntaxError(sqlCommand, token.start(), "integer");
}
+ Value value = token.value(session);
if (minus) {
// must do that now, otherwise Integer.MIN_VALUE would not work
- currentValue = currentValue.negate();
+ value = value.negate();
}
- int i = currentValue.getInt();
+ int i = value.getInt();
read();
return i;
}
@@ -6057,13 +5704,14 @@ private long readLong() {
read();
}
if (currentTokenType != LITERAL) {
- throw DbException.getSyntaxError(sqlCommand, parseIndex, "long");
+ throw DbException.getSyntaxError(sqlCommand, token.start(), "long");
}
+ Value value = token.value(session);
if (minus) {
// must do that now, otherwise Long.MIN_VALUE would not work
- currentValue = currentValue.negate();
+ value = value.negate();
}
- long i = currentValue.getLong();
+ long i = value.getLong();
read();
return i;
}
@@ -6078,7 +5726,7 @@ private boolean readBooleanSetting() {
read();
return false;
case LITERAL:
- boolean result = currentValue.getBoolean();
+ boolean result = token.value(session).getBoolean();
read();
return result;
}
@@ -6093,7 +5741,7 @@ private boolean readBooleanSetting() {
}
private String readString() {
- int index = parseIndex;
+ int sqlIndex = token.start();
Expression expr = readExpression();
try {
String s = expr.optimize(session).getValue(session).getString();
@@ -6102,7 +5750,7 @@ private String readString() {
}
} catch (DbException e) {
}
- throw DbException.getSyntaxError(sqlCommand, index, "character string");
+ throw DbException.getSyntaxError(sqlCommand, sqlIndex, "character string");
}
// TODO: why does this function allow defaultSchemaName=null - which resets
@@ -6148,7 +5796,7 @@ private String readIdentifier() {
* allow migration from older versions.
*/
if (!session.isQuirksMode() || !isKeyword(currentTokenType)) {
- throw DbException.getSyntaxError(sqlCommand, parseIndex, "identifier");
+ throw DbException.getSyntaxError(sqlCommand, token.start(), "identifier");
}
}
String s = currentToken;
@@ -6157,7 +5805,7 @@ private String readIdentifier() {
}
private void read(String expected) {
- if (currentTokenQuoted || !equalsToken(expected, currentToken)) {
+ if (token.isQuoted() || !equalsToken(expected, currentToken)) {
addExpected(expected);
throw getSyntaxError();
}
@@ -6172,12 +5820,12 @@ private void read(int tokenType) {
read();
}
- private boolean readIf(String token) {
- if (!currentTokenQuoted && equalsToken(token, currentToken)) {
+ private boolean readIf(String tokenName) {
+ if (!token.isQuoted() && equalsToken(tokenName, currentToken)) {
read();
return true;
}
- addExpected(token);
+ addExpected(tokenName);
return false;
}
@@ -6190,11 +5838,11 @@ private boolean readIf(int tokenType) {
return false;
}
- private boolean isToken(String token) {
- if (!currentTokenQuoted && equalsToken(token, currentToken)) {
+ private boolean isToken(String tokenName) {
+ if (!token.isQuoted() && equalsToken(tokenName, currentToken)) {
return true;
}
- addExpected(token);
+ addExpected(tokenName);
return false;
}
@@ -6235,786 +5883,66 @@ private void addMultipleExpected(int ... tokenTypes) {
}
}
- private void reread(int index) {
- if (lastParseIndex != index) {
- parseIndex = index;
- read();
- }
- }
-
private void read() {
- currentTokenQuoted = false;
if (expectedList != null) {
expectedList.clear();
}
- int[] types = characterTypes;
- lastParseIndex = parseIndex;
- int i = parseIndex;
- int type;
- while ((type = types[i]) == 0) {
- i++;
- }
- int start = i;
- char[] chars = sqlCommandChars;
- char c = chars[i++];
- currentToken = "";
- switch (type) {
- case CHAR_NAME:
- switch (c) {
- case 'N':
- case 'n':
- if (chars[i] == '\'') {
- readString(i + 1, chars, types);
- return;
- }
- break;
- case 'X':
- case 'x':
- if (chars[i] == '\'') {
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- for (;;) {
- int begin = ++i;
- while (chars[i] != '\'') {
- i++;
- }
- StringUtils.convertHexWithSpacesToBytes(result, sqlCommandChars, begin, i);
- begin = ++i;
- while ((type = types[i]) == 0) {
- i++;
- }
- if (begin == i || type != CHAR_STRING) {
- break;
- }
- }
- currentToken = "X'";
- checkLiterals(true);
- currentValue = ValueVarbinary.get(result.toByteArray());
- parseIndex = i;
- currentTokenType = LITERAL;
- return;
- }
- break;
- case 'U':
- case 'u':
- if (chars[i] == '&') {
- switch (chars[i + 1]) {
- case '\'': {
- String s = readRawString(i + 2, chars, types);
- currentValue = ValueVarchar.get(StringUtils.decodeUnicodeStringSQL(s,
- readUescape(parseIndex, chars, types)));
- return;
- }
- case '"': {
- readQuotedIdentifier(i + 2, '"', chars, false);
- String identifier = currentToken;
- i = parseIndex;
- while (types[i] == 0) {
- i++;
- }
- identifier = StringUtils.decodeUnicodeStringSQL(identifier, readUescape(i, chars, types));
- if (identifier.length() > Constants.MAX_IDENTIFIER_LENGTH) {
- throw DbException.get(ErrorCode.NAME_TOO_LONG_2, identifier.substring(0, 32),
- "" + Constants.MAX_IDENTIFIER_LENGTH);
- }
- currentToken = StringUtils.cache(identifier);
- currentTokenQuoted = true;
- currentTokenType = IDENTIFIER;
- return;
- }
- }
- }
- }
- while ((type = types[i]) == CHAR_NAME || type == CHAR_VALUE) {
- i++;
- }
- currentTokenType = ParserUtil.getTokenType(sqlCommand, !identifiersToUpper, start, i - start, false);
- switch (currentTokenType) {
- case LIMIT:
- if (!database.getMode().limit) {
- currentTokenType = IDENTIFIER;
- }
- break;
- case MINUS:
- if (!database.getMode().minusIsExcept) {
- currentTokenType = IDENTIFIER;
- }
- }
- if (isIdentifier()) {
- currentToken = StringUtils.cache(checkIdentifierLength(start, i));
- } else {
- currentToken = TOKENS[currentTokenType];
- }
- parseIndex = i;
- return;
- case CHAR_QUOTED:
- readQuotedIdentifier(i, c, chars, true);
- return;
- case CHAR_SPECIAL_2:
- if (types[i] == CHAR_SPECIAL_2) {
- char c1 = chars[i++];
- currentTokenType = getSpecialType2(c, c1);
- } else {
- currentTokenType = getSpecialType1(c);
+ int size = tokens.size();
+ if (tokenIndex + 1 < size) {
+ token = tokens.get(++tokenIndex);
+ currentTokenType = token.tokenType();
+ currentToken = token.asIdentifier();
+ if (currentToken != null && currentToken.length() > Constants.MAX_IDENTIFIER_LENGTH) {
+ throw DbException.get(ErrorCode.NAME_TOO_LONG_2, currentToken.substring(0, 32),
+ "" + Constants.MAX_IDENTIFIER_LENGTH);
+ } else if (currentTokenType == LITERAL) {
+ checkLiterals();
}
- parseIndex = i;
- return;
- case CHAR_SPECIAL_1:
- currentTokenType = getSpecialType1(c);
- parseIndex = i;
- return;
- case CHAR_VALUE:
- if (c == '0' && (chars[i] == 'X' || chars[i] == 'x')) {
- readHexNumber(i + 1, start + 2, chars, types);
- return;
- }
- long number = c - '0';
- loop: for (;; i++) {
- c = chars[i];
- if (c < '0' || c > '9') {
- switch (c) {
- case '.':
- readNumeric(start, i, false, false);
- break loop;
- case 'E':
- case 'e':
- readNumeric(start, i, false, true);
- break loop;
- case 'L':
- case 'l':
- readNumeric(start, i, true, false);
- break loop;
- }
- checkLiterals(false);
- currentValue = ValueInteger.get((int) number);
- currentTokenType = LITERAL;
- currentToken = "0";
- parseIndex = i;
- break;
- }
- number = number * 10 + (c - '0');
- if (number > Integer.MAX_VALUE) {
- readNumeric(start, i, true, false);
- break;
- }
- }
- return;
- case CHAR_DOT:
- if (types[i] != CHAR_VALUE) {
- currentTokenType = DOT;
- currentToken = ".";
- parseIndex = i;
- return;
- }
- readNumeric(i - 1, i, false, false);
- return;
- case CHAR_STRING:
- readString(i, chars, types);
- return;
- case CHAR_DOLLAR_QUOTED_STRING: {
- int begin = i - 1;
- while (types[i] == CHAR_DOLLAR_QUOTED_STRING) {
- i++;
- }
- String result = sqlCommand.substring(begin, i);
- currentToken = "'";
- checkLiterals(true);
- currentValue = ValueVarchar.get(result, database);
- parseIndex = i;
- currentTokenType = LITERAL;
- return;
- }
- case CHAR_END:
- currentTokenType = END_OF_INPUT;
- parseIndex = i;
- return;
- default:
+ } else {
throw getSyntaxError();
}
}
- private void readQuotedIdentifier(int i, char c, char[] chars, boolean checkLength) {
- int begin = i;
- while (chars[i] != c) {
- i++;
- }
- String result = checkLength ? checkIdentifierLength(begin, i) : sqlCommand.substring(begin, i);
- if (chars[++i] == c) {
- StringBuilder builder = new StringBuilder(result);
- do {
- begin = i;
- while (chars[++i] != c) {}
- if (checkLength) {
- checkIdentifierLength(builder, begin, i);
- }
- } while (chars[++i] == c);
- result = builder.toString();
- }
- currentToken = StringUtils.cache(result);
- parseIndex = i;
- currentTokenQuoted = true;
- currentTokenType = IDENTIFIER;
- }
-
- private String checkIdentifierLength(int begin, int end) {
- if (end - begin > Constants.MAX_IDENTIFIER_LENGTH) {
- throw DbException.get(ErrorCode.NAME_TOO_LONG_2, sqlCommand.substring(begin, begin + 32),
- "" + Constants.MAX_IDENTIFIER_LENGTH);
- }
- return sqlCommand.substring(begin, end);
- }
-
- private void checkIdentifierLength(StringBuilder builder, int begin, int end) {
- int length = builder.length();
- if (length + end - begin > Constants.MAX_IDENTIFIER_LENGTH) {
- if (length < 32) {
- builder.append(sqlCommand, begin, begin + 32 - length);
- } else {
- builder.setLength(32);
- }
- throw DbException.get(ErrorCode.NAME_TOO_LONG_2, builder.toString(), "" + Constants.MAX_IDENTIFIER_LENGTH);
- }
- builder.append(sqlCommand, begin, end);
- }
-
- private void readParameterIndex() {
- int i = parseIndex;
- char[] chars = sqlCommandChars;
- char c = chars[i++];
- long number = c - '0';
- for (; (c = chars[i]) >= '0' && c <= '9'; i++) {
- number = number * 10 + (c - '0');
- if (number > Integer.MAX_VALUE) {
- throw DbException.getInvalidValueException(
- "parameter index", number);
- }
- }
- currentValue = ValueInteger.get((int) number);
- currentTokenType = LITERAL;
- currentToken = "0";
- parseIndex = i;
- }
-
- private void checkLiterals(boolean text) {
+ private void checkLiterals() {
if (!literalsChecked && session != null && !session.getAllowLiterals()) {
int allowed = database.getAllowLiterals();
- if (allowed == Constants.ALLOW_LITERALS_NONE ||
- (text && allowed != Constants.ALLOW_LITERALS_ALL)) {
+ if (allowed == Constants.ALLOW_LITERALS_NONE
+ || ((token instanceof Token.CharacterStringToken || token instanceof Token.BinaryStringToken)
+ && allowed != Constants.ALLOW_LITERALS_ALL)) {
throw DbException.get(ErrorCode.LITERALS_ARE_NOT_ALLOWED);
}
}
}
- private void readString(int i, char[] chars, int[] types) {
- currentValue = ValueVarchar.get(readRawString(i, chars, types), database);
- }
-
- private String readRawString(int i, char[] chars, int[] types) {
- String result = null;
- StringBuilder builder = null;
- for (;; i++) {
- boolean next = false;
- for (;; i++) {
- int begin = i;
- while (chars[i] != '\'') {
- i++;
- }
- if (result == null) {
- result = sqlCommand.substring(begin, i);
- } else {
- if (builder == null) {
- builder = new StringBuilder(result);
- }
- builder.append(sqlCommand, next ? begin - 1 : begin, i);
- }
- if (chars[++i] != '\'') {
- break;
- }
- next = true;
- }
- int type;
- while ((type = types[i]) == 0) {
- i++;
- }
- if (type != CHAR_STRING) {
- break;
- }
- }
- checkLiterals(true);
- parseIndex = i;
- currentToken = "'";
- currentTokenType = LITERAL;
- return builder != null ? builder.toString() : result;
- }
-
- private int readUescape(int i, char[] chars, int[] types) {
- int start = i;
- while (types[i] == CHAR_NAME) {
- i++;
- }
- if (i - start == 7 && "UESCAPE".regionMatches(!identifiersToUpper, 0, sqlCommand, start, 7)) {
- int type;
- while ((type = types[i]) == 0) {
- i++;
- }
- if (type == CHAR_STRING) {
- String s = readRawString(i + 1, chars, types);
- if (s.codePointCount(0, s.length()) == 1) {
- int escape = s.codePointAt(0);
- if (!Character.isWhitespace(escape) && (escape < '0' || escape > '9')
- && (escape < 'A' || escape > 'F') && (escape < 'a' || escape > 'f')) {
- switch (escape) {
- default:
- return escape;
- case '"':
- case '\'':
- case '+':
- }
- }
- }
- }
- addExpected("''");
- throw getSyntaxError();
- }
- return '\\';
- }
-
- private void readHexNumber(int i, int start, char[] chars, int[] types) {
- if (database.getMode().zeroExLiteralsAreBinaryStrings) {
- for (char c; (c = chars[i]) >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'z';) {
- i++;
- }
- if (types[i] == CHAR_NAME) {
- throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, sqlCommand.substring(i, i + 1));
- }
- checkLiterals(true);
- currentValue = ValueVarbinary.getNoCopy(StringUtils.convertHexToBytes(sqlCommand.substring(start, i)));
- parseIndex = i;
- } else {
- long number = 0;
- for (;; i++) {
- char c = chars[i];
- if (c >= '0' && c <= '9') {
- number = (number << 4) + c - '0';
- } else if ((c &= 0xffdf) >= 'A' && c <= 'F') { // Convert a-z to A-Z
- number = (number << 4) + c - ('A' - 10);
- } else if (i == start) {
- parseIndex = i;
- addExpected("Hex number");
- throw getSyntaxError();
- } else {
- currentValue = ValueInteger.get((int) number);
- break;
- }
- if (number > Integer.MAX_VALUE) {
- do {
- c = chars[++i];
- } while ((c >= '0' && c <= '9') || ((c &= 0xffdf) >= 'A' && c <= 'F')); // Convert a-z to A-Z
- String sub = sqlCommand.substring(start, i);
- currentValue = ValueNumeric.get(new BigInteger(sub, 16));
- break;
- }
- }
- char c = chars[i];
- if (c == 'L' || c == 'l') {
- i++;
- }
- parseIndex = i;
- if (types[i] == CHAR_NAME) {
- addExpected("Hex number");
- throw getSyntaxError();
- }
- checkLiterals(false);
- }
- currentTokenType = LITERAL;
- currentToken = "0";
- }
-
- private void readNumeric(int start, int i, boolean integer, boolean approximate) {
- char[] chars = sqlCommandChars;
- int[] types = characterTypes;
- // go until the first non-number
- for (;; i++) {
- int t = types[i];
- if (t == CHAR_DOT) {
- integer = false;
- } else if (t != CHAR_VALUE) {
- break;
- }
- }
- char c = chars[i];
- if (c == 'E' || c == 'e') {
- integer = false;
- approximate = true;
- c = chars[++i];
- if (c == '+' || c == '-') {
- i++;
- }
- if (types[i] != CHAR_VALUE) {
- throw getSyntaxError();
- }
- while (types[++i] == CHAR_VALUE) {
- // go until the first non-number
- }
- }
- parseIndex = i;
- checkLiterals(false);
- if (integer && i - start <= 19) {
- BigInteger bi = new BigInteger(sqlCommand.substring(start, i));
- if (bi.compareTo(ValueBigint.MAX_BI) <= 0) {
- // parse constants like "10000000L"
- c = chars[i];
- if (c == 'L' || c == 'l') {
- parseIndex++;
- }
- currentValue = ValueBigint.get(bi.longValue());
- currentTokenType = LITERAL;
- return;
- }
- currentValue = ValueNumeric.get(bi);
- } else {
- BigDecimal bd;
- try {
- bd = new BigDecimal(sqlCommandChars, start, i - start);
- } catch (NumberFormatException e) {
- throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, sqlCommand.substring(start, i));
- }
- currentValue = approximate ? ValueDecfloat.get(bd) : ValueNumeric.get(bd);
- }
- currentTokenType = LITERAL;
- }
-
- private void initialize(String sql) {
+ private void initialize(String sql, ArrayList tokens, boolean stopOnCloseParen) {
if (sql == null) {
sql = "";
}
- originalSQL = sql;
sqlCommand = sql;
- int len = sql.length() + 1;
- char[] command = new char[len];
- int[] types = new int[len];
- len--;
- sql.getChars(0, len, command, 0);
- boolean changed = false;
- command[len] = ' ';
- int startLoop = 0;
- int lastType = 0;
- for (int i = 0; i < len; i++) {
- char c = command[i];
- int type = 0;
- switch (c) {
- case '/':
- if (command[i + 1] == '*') {
- // block comment
- changed = true;
- command[i] = ' ';
- command[i + 1] = ' ';
- startLoop = i;
- i += 2;
- for (int level = 1; level > 0;) {
- for (;;) {
- checkRunOver(i, len, startLoop);
- char ch = command[i];
- command[i++] = ' ';
- if (ch == '*') {
- if (command[i] == '/') {
- level--;
- break;
- }
- } else if (ch == '/' && command[i] == '*') {
- level++;
- command[i++] = ' ';
- }
- }
- command[i] = ' ';
- }
- } else if (command[i + 1] == '/') {
- // single line comment
- changed = true;
- startLoop = i;
- while ((c = command[i]) != '\n' && c != '\r' && i < len - 1) {
- command[i++] = ' ';
- checkRunOver(i, len, startLoop);
- }
- } else {
- type = CHAR_SPECIAL_1;
- }
- break;
- case '-':
- if (command[i + 1] == '-') {
- // single line comment
- changed = true;
- startLoop = i;
- while ((c = command[i]) != '\n' && c != '\r' && i < len - 1) {
- command[i++] = ' ';
- checkRunOver(i, len, startLoop);
- }
- } else {
- type = CHAR_SPECIAL_1;
- }
- break;
- case '$':
- if (command[i + 1] == '$' && (i == 0 || command[i - 1] <= ' ')) {
- // dollar quoted string
- changed = true;
- command[i] = ' ';
- command[i + 1] = ' ';
- startLoop = i;
- i += 2;
- checkRunOver(i, len, startLoop);
- while (command[i] != '$' || command[i + 1] != '$') {
- types[i++] = CHAR_DOLLAR_QUOTED_STRING;
- checkRunOver(i, len, startLoop);
- }
- command[i] = ' ';
- command[i + 1] = ' ';
- i++;
- } else {
- if (lastType == CHAR_NAME || lastType == CHAR_VALUE) {
- // $ inside an identifier is supported
- type = CHAR_NAME;
- } else {
- // but not at the start, to support PostgreSQL $1
- type = CHAR_SPECIAL_1;
- }
- }
- break;
- case '(':
- case ')':
- case '{':
- case '}':
- case '*':
- case ',':
- case ';':
- case '+':
- case '%':
- case '@':
- case ']':
- type = CHAR_SPECIAL_1;
- break;
- case '!':
- case '<':
- case '>':
- case '|':
- case '=':
- case ':':
- case '&':
- case '~':
- type = CHAR_SPECIAL_2;
- break;
- case '.':
- type = CHAR_DOT;
- break;
- case '\'':
- type = types[i] = CHAR_STRING;
- startLoop = i;
- while (command[++i] != '\'') {
- checkRunOver(i, len, startLoop);
- }
- break;
- case '?':
- type = CHAR_SPECIAL_1;
- if (command[i + 1] == '?') {
- char ch = command[i + 2];
- if (ch == '(') {
- command[i + 1] = command[i] = ' ';
- command[i += 2] = '[';
- changed = true;
- } else if (ch == ')') {
- command[i + 1] = command[i] = ' ';
- command[i += 2] = ']';
- changed = true;
- }
- }
- break;
- case '[':
- if (database.getMode().squareBracketQuotedNames) {
- // SQL Server alias for "
- command[i] = '"';
- changed = true;
- type = types[i] = CHAR_QUOTED;
- startLoop = i;
- while (command[++i] != ']') {
- checkRunOver(i, len, startLoop);
- }
- command[i] = '"';
- } else {
- type = CHAR_SPECIAL_1;
- }
- break;
- case '`':
- // MySQL alias for ", but not case sensitive
- type = types[i] = CHAR_QUOTED;
- startLoop = i;
- while (command[++i] != '`') {
- checkRunOver(i, len, startLoop);
- c = command[i];
- if (identifiersToUpper || identifiersToLower) {
- char u = identifiersToUpper ? Character.toUpperCase(c) : Character.toLowerCase(c);
- if (u != c) {
- command[i] = u;
- changed = true;
- }
- }
- }
- break;
- case '"':
- type = types[i] = CHAR_QUOTED;
- startLoop = i;
- while (command[++i] != '"') {
- checkRunOver(i, len, startLoop);
- }
- break;
- case '_':
- type = CHAR_NAME;
- break;
- case '#':
- if (database.getMode().supportPoundSymbolForColumnNames) {
- type = CHAR_NAME;
- } else {
- type = CHAR_SPECIAL_1;
- }
- break;
- default:
- if (c >= 'a' && c <= 'z') {
- if (identifiersToUpper) {
- command[i] = (char) (c - ('a' - 'A'));
- changed = true;
- }
- type = CHAR_NAME;
- } else if (c >= 'A' && c <= 'Z') {
- if (identifiersToLower) {
- command[i] = (char) (c + ('a' - 'A'));
- changed = true;
- }
- type = CHAR_NAME;
- } else if (c >= '0' && c <= '9') {
- type = CHAR_VALUE;
- } else {
- if (c <= ' ' || Character.isSpaceChar(c)) {
- // whitespace
- } else if (Character.isJavaIdentifierPart(c)) {
- type = CHAR_NAME;
- if (identifiersToUpper || identifiersToLower) {
- char u = identifiersToUpper ? Character.toUpperCase(c) : Character.toLowerCase(c);
- if (u != c) {
- command[i] = u;
- changed = true;
- }
- }
- } else {
- type = CHAR_SPECIAL_1;
- }
- }
- }
- types[i] = type;
- lastType = type;
- }
- sqlCommandChars = command;
- types[len] = CHAR_END;
- characterTypes = types;
- if (changed) {
- sqlCommand = new String(command, 0, len);
- }
- parseIndex = 0;
+ this.tokens = tokens == null
+ ? Tokenizer.tokenize(sql, database, identifiersToUpper, identifiersToLower, nonKeywords,
+ stopOnCloseParen)
+ : tokens;
+ resetTokenIndex();
}
- private void checkRunOver(int i, int len, int startLoop) {
- if (i >= len) {
- parseIndex = startLoop;
- throw getSyntaxError();
- }
+ private void resetTokenIndex() {
+ tokenIndex = -1;
+ token = null;
+ currentTokenType = -1;
+ currentToken = null;
}
- private int getSpecialType1(char c0) {
- switch (c0) {
- case '?':
- case '$':
- return PARAMETER;
- case '@':
- return AT;
- case '+':
- return PLUS_SIGN;
- case '-':
- return MINUS_SIGN;
- case '*':
- return ASTERISK;
- case ',':
- return COMMA;
- case '{':
- return OPEN_BRACE;
- case '}':
- return CLOSE_BRACE;
- case '/':
- return SLASH;
- case '%':
- return PERCENT;
- case '&':
- return AMPERSAND;
- case ';':
- return SEMICOLON;
- case ':':
- return COLON;
- case '[':
- return OPEN_BRACKET;
- case ']':
- return CLOSE_BRACKET;
- case '~':
- return TILDE;
- case '(':
- return OPEN_PAREN;
- case ')':
- return CLOSE_PAREN;
- case '<':
- return SMALLER;
- case '>':
- return BIGGER;
- case '=':
- return EQUAL;
- default:
- throw getSyntaxError();
- }
- }
-
- private int getSpecialType2(char c0, char c1) {
- switch (c0) {
- case ':':
- if (c1 == ':') {
- return COLON_COLON;
- } else if (c1 == '=') {
- return COLON_EQ;
- }
- break;
- case '>':
- if (c1 == '=') {
- return BIGGER_EQUAL;
- }
- break;
- case '<':
- if (c1 == '=') {
- return SMALLER_EQUAL;
- } else if (c1 == '>') {
- return NOT_EQUAL;
- }
- break;
- case '!':
- if (c1 == '=') {
- return NOT_EQUAL;
- } else if (c1 == '~') {
- return NOT_TILDE;
- }
- break;
- case '|':
- if (c1 == '|') {
- return CONCATENATION;
- }
- break;
- case '&':
- if (c1 == '&') {
- return SPATIAL_INTERSECTS;
+ void setTokenIndex(int index) {
+ if (index != tokenIndex) {
+ if (expectedList != null) {
+ expectedList.clear();
}
- break;
+ token = tokens.get(index);
+ tokenIndex = index;
+ currentTokenType = token.tokenType();
+ currentToken = token.asIdentifier();
}
- throw getSyntaxError();
}
private static boolean isKeyword(int tokenType) {
@@ -7198,7 +6126,7 @@ private TypeInfo readIfDataType() {
private TypeInfo readIfDataType1() {
switch (currentTokenType) {
case IDENTIFIER:
- if (currentTokenQuoted) {
+ if (token.isQuoted()) {
return null;
}
break;
@@ -7231,11 +6159,11 @@ private TypeInfo readIfDataType1() {
addExpected("data type");
throw getSyntaxError();
}
- int index = lastParseIndex;
+ int index = tokenIndex;
String originalCase = currentToken;
read();
if (currentTokenType == DOT) {
- reread(index);
+ setTokenIndex(index);
return null;
}
String original = upperName(originalCase);
@@ -7338,7 +6266,7 @@ private TypeInfo readIfDataType1() {
if (originalCase.length() == original.length()) {
Domain domain = database.getSchema(session.getCurrentSchemaName()).findDomain(originalCase);
if (domain != null) {
- reread(index);
+ setTokenIndex(index);
return null;
}
}
@@ -7737,14 +6665,14 @@ private TypeInfo parseGeometryType() {
ExtTypeInfoGeometry extTypeInfo;
if (readIf(OPEN_PAREN)) {
int type = 0;
- if (currentTokenType != IDENTIFIER || currentTokenQuoted) {
+ if (currentTokenType != IDENTIFIER || token.isQuoted()) {
throw getSyntaxError();
}
if (!readIf("GEOMETRY")) {
try {
type = EWKTUtils.parseGeometryType(currentToken);
read();
- if (type / 1_000 == 0 && currentTokenType == IDENTIFIER && !currentTokenQuoted) {
+ if (type / 1_000 == 0 && currentTokenType == IDENTIFIER && !token.isQuoted()) {
type += EWKTUtils.parseDimensionSystem(currentToken) * 1_000;
read();
}
@@ -7778,7 +6706,7 @@ private TypeInfo parseRowType() {
private long readPrecision(int valueType) {
long p = readPositiveLong();
- if (currentTokenType != IDENTIFIER || currentTokenQuoted) {
+ if (currentTokenType != IDENTIFIER || token.isQuoted()) {
return p;
}
if ((valueType == Value.BLOB || valueType == Value.CLOB) && currentToken.length() == 1) {
@@ -7811,7 +6739,7 @@ private long readPrecision(int valueType) {
}
p *= mul;
read();
- if (currentTokenType != IDENTIFIER || currentTokenQuoted) {
+ if (currentTokenType != IDENTIFIER || token.isQuoted()) {
return p;
}
}
@@ -8082,7 +7010,7 @@ private ArrayList parseValuesRow(ArrayList row) {
private Call parseCall() {
Call command = new Call(session);
currentPrepared = command;
- int index = lastParseIndex;
+ int index = tokenIndex;
boolean canBeFunction;
switch (currentTokenType) {
case IDENTIFIER:
@@ -8100,7 +7028,7 @@ private Call parseCall() {
command.setExpression(readExpression());
} catch (DbException e) {
if (canBeFunction && e.getErrorCode() == ErrorCode.FUNCTION_NOT_FOUND_1) {
- reread(index);
+ setTokenIndex(index);
String schemaName = null, name = readIdentifier();
if (readIf(DOT)) {
schemaName = name;
@@ -8446,7 +7374,7 @@ private Prepared parseWith1(List viewsCreated) {
// used in setCteCleanups.
Collections.reverse(viewsCreated);
- int start = lastParseIndex;
+ int start = tokenIndex;
if (isQueryQuick()) {
p = parseWithQuery();
} else if (readIf("INSERT")) {
@@ -8628,9 +7556,8 @@ private CreateView parseCreateView(boolean force, boolean orReplace) {
String[] cols = parseColumnList();
command.setColumnNames(cols);
}
- String select = StringUtils.cache(sqlCommand
- .substring(parseIndex));
read(AS);
+ String select = StringUtils.cache(sqlCommand.substring(token.start()));
try {
Query query;
session.setParsingCreateView(true);
@@ -8888,10 +7815,10 @@ private boolean parseSequenceOptions(SequenceOptions options, CreateSequence com
} else if (command != null && parseCreateSequenceOption(command)) {
//
} else if (forAlterColumn) {
- int index = lastParseIndex;
+ int index = tokenIndex;
if (readIf(SET)) {
if (!parseBasicSequenceOption(options)) {
- reread(index);
+ setTokenIndex(index);
break;
}
} else {
@@ -9717,7 +8644,7 @@ private Prepared getAlterTableAlterColumnDropDefaultExpression(Schema schema, St
private Prepared parseAlterTableAlterColumnIdentity(Schema schema, String tableName, boolean ifTableExists,
Column column) {
- int index = lastParseIndex;
+ int index = tokenIndex;
Boolean always = null;
if (readIf(SET) && readIf("GENERATED")) {
if (readIf("ALWAYS")) {
@@ -9728,7 +8655,7 @@ private Prepared parseAlterTableAlterColumnIdentity(Schema schema, String tableN
always = false;
}
} else {
- reread(index);
+ setTokenIndex(index);
}
SequenceOptions options = new SequenceOptions();
if (!parseSequenceOptions(options, null, false, true) && always == null) {
@@ -10262,7 +9189,7 @@ private DefineCommand parseTableConstraintIf(String tableName, Schema schema, bo
if (constraintName == null) {
Mode mode = database.getMode();
if (mode.indexDefinitionInCreateTable) {
- int start = lastParseIndex;
+ int start = tokenIndex;
if (readIf(KEY) || readIf("INDEX")) {
// MySQL
// need to read ahead, as it could be a column name
@@ -10283,7 +9210,7 @@ private DefineCommand parseTableConstraintIf(String tableName, Schema schema, bo
return createIndex;
} else {
// known data type
- reread(start);
+ setTokenIndex(start);
}
}
}
@@ -10765,7 +9692,7 @@ public void setSuppliedParameterList(ArrayList suppliedParameterList)
*/
public Expression parseExpression(String sql) {
parameters = Utils.newSmallArrayList();
- initialize(sql);
+ initialize(sql, null, false);
read();
return readExpression();
}
@@ -10778,7 +9705,7 @@ public Expression parseExpression(String sql) {
*/
public Expression parseDomainConstraintExpression(String sql) {
parameters = Utils.newSmallArrayList();
- initialize(sql);
+ initialize(sql, null, false);
read();
try {
parseDomainConstraint = true;
@@ -10796,7 +9723,7 @@ public Expression parseDomainConstraintExpression(String sql) {
*/
public Table parseTableName(String sql) {
parameters = Utils.newSmallArrayList();
- initialize(sql);
+ initialize(sql, null, false);
read();
return readTableOrView();
}
@@ -10811,9 +9738,13 @@ public Table parseTableName(String sql) {
* @throws DbException on syntax error
*/
public Object parseColumnList(String sql, int offset) {
- initialize(sql);
- parseIndex = offset;
- read();
+ initialize(sql, null, true);
+ for (int i = 0, l = tokens.size(); i < l; i++) {
+ if (tokens.get(i).start() >= offset) {
+ setTokenIndex(i);
+ break;
+ }
+ }
read(OPEN_PAREN);
if (readIf(CLOSE_PAREN)) {
return Utils.EMPTY_INT_ARRAY;
@@ -10850,11 +9781,11 @@ public Object parseColumnList(String sql, int offset) {
* @return the last parse index
*/
public int getLastParseIndex() {
- return lastParseIndex;
+ return token.start();
}
@Override
public String toString() {
- return StringUtils.addAsterisk(sqlCommand, parseIndex);
+ return StringUtils.addAsterisk(sqlCommand, token.start());
}
}
diff --git a/h2/src/main/org/h2/command/Prepared.java b/h2/src/main/org/h2/command/Prepared.java
index 4b7c88ed09..f9a88835d9 100644
--- a/h2/src/main/org/h2/command/Prepared.java
+++ b/h2/src/main/org/h2/command/Prepared.java
@@ -36,6 +36,11 @@ public abstract class Prepared {
*/
protected String sqlStatement;
+ /**
+ * The SQL tokens.
+ */
+ protected ArrayList sqlTokens;
+
/**
* Whether to create a new object (for indexes).
*/
@@ -234,9 +239,11 @@ public ResultInterface query(long maxrows) {
* Set the SQL statement.
*
* @param sql the SQL statement
+ * @param sqlTokens the SQL tokens
*/
- public void setSQL(String sql) {
+ public final void setSQL(String sql, ArrayList sqlTokens) {
this.sqlStatement = sql;
+ this.sqlTokens = sqlTokens;
}
/**
@@ -244,10 +251,19 @@ public void setSQL(String sql) {
*
* @return the SQL statement
*/
- public String getSQL() {
+ public final String getSQL() {
return sqlStatement;
}
+ /**
+ * Get the SQL tokens.
+ *
+ * @return the SQL tokens
+ */
+ public final ArrayList getSQLTokens() {
+ return sqlTokens;
+ }
+
/**
* Get the object id to use for the database object that is created in this
* statement. This id is only set when the object is already persisted.
diff --git a/h2/src/main/org/h2/command/Token.java b/h2/src/main/org/h2/command/Token.java
new file mode 100644
index 0000000000..aec66e50e1
--- /dev/null
+++ b/h2/src/main/org/h2/command/Token.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
+ * and the EPL 1.0 (https://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.h2.command;
+
+import static org.h2.util.ParserUtil.IDENTIFIER;
+import static org.h2.util.ParserUtil.LAST_KEYWORD;
+
+import org.h2.engine.CastDataProvider;
+import org.h2.message.DbException;
+import org.h2.util.StringUtils;
+import org.h2.value.Value;
+import org.h2.value.ValueBigint;
+import org.h2.value.ValueInteger;
+import org.h2.value.ValueVarbinary;
+import org.h2.value.ValueVarchar;
+
+/**
+ * Token.
+ */
+public abstract class Token implements Cloneable {
+
+ /**
+ * Token with parameter.
+ */
+ static final int PARAMETER = LAST_KEYWORD + 1;
+
+ /**
+ * End of input.
+ */
+ static final int END_OF_INPUT = PARAMETER + 1;
+
+ /**
+ * Token with literal.
+ */
+ static final int LITERAL = END_OF_INPUT + 1;
+
+ /**
+ * The token "=".
+ */
+ static final int EQUAL = LITERAL + 1;
+
+ /**
+ * The token ">=".
+ */
+ static final int BIGGER_EQUAL = EQUAL + 1;
+
+ /**
+ * The token ">".
+ */
+ static final int BIGGER = BIGGER_EQUAL + 1;
+
+ /**
+ * The token "<".
+ */
+ static final int SMALLER = BIGGER + 1;
+
+ /**
+ * The token "<=".
+ */
+ static final int SMALLER_EQUAL = SMALLER + 1;
+
+ /**
+ * The token "<>" or "!=".
+ */
+ static final int NOT_EQUAL = SMALLER_EQUAL + 1;
+
+ /**
+ * The token "@".
+ */
+ static final int AT = NOT_EQUAL + 1;
+
+ /**
+ * The token "-".
+ */
+ static final int MINUS_SIGN = AT + 1;
+
+ /**
+ * The token "+".
+ */
+ static final int PLUS_SIGN = MINUS_SIGN + 1;
+
+ /**
+ * The token "||".
+ */
+ static final int CONCATENATION = PLUS_SIGN + 1;
+
+ /**
+ * The token "(".
+ */
+ static final int OPEN_PAREN = CONCATENATION + 1;
+
+ /**
+ * The token ")".
+ */
+ static final int CLOSE_PAREN = OPEN_PAREN + 1;
+
+ /**
+ * The token "&&".
+ */
+ static final int SPATIAL_INTERSECTS = CLOSE_PAREN + 1;
+
+ /**
+ * The token "*".
+ */
+ static final int ASTERISK = SPATIAL_INTERSECTS + 1;
+
+ /**
+ * The token ",".
+ */
+ static final int COMMA = ASTERISK + 1;
+
+ /**
+ * The token ".".
+ */
+ static final int DOT = COMMA + 1;
+
+ /**
+ * The token "{".
+ */
+ static final int OPEN_BRACE = DOT + 1;
+
+ /**
+ * The token "}".
+ */
+ static final int CLOSE_BRACE = OPEN_BRACE + 1;
+
+ /**
+ * The token "/".
+ */
+ static final int SLASH = CLOSE_BRACE + 1;
+
+ /**
+ * The token "%".
+ */
+ static final int PERCENT = SLASH + 1;
+
+ /**
+ * The token ";".
+ */
+ static final int SEMICOLON = PERCENT + 1;
+
+ /**
+ * The token ":".
+ */
+ static final int COLON = SEMICOLON + 1;
+
+ /**
+ * The token "[".
+ */
+ static final int OPEN_BRACKET = COLON + 1;
+
+ /**
+ * The token "]".
+ */
+ static final int CLOSE_BRACKET = OPEN_BRACKET + 1;
+
+ /**
+ * The token "~".
+ */
+ static final int TILDE = CLOSE_BRACKET + 1;
+
+ /**
+ * The token "::".
+ */
+ static final int COLON_COLON = TILDE + 1;
+
+ /**
+ * The token ":=".
+ */
+ static final int COLON_EQ = COLON_COLON + 1;
+
+ /**
+ * The token "!~".
+ */
+ static final int NOT_TILDE = COLON_EQ + 1;
+
+ static final String[] TOKENS = {
+ // Unused
+ null,
+ // KEYWORD
+ null,
+ // IDENTIFIER
+ null,
+ // ALL
+ "ALL",
+ // AND
+ "AND",
+ // ANY
+ "ANY",
+ // ARRAY
+ "ARRAY",
+ // AS
+ "AS",
+ // ASYMMETRIC
+ "ASYMMETRIC",
+ // AUTHORIZATION
+ "AUTHORIZATION",
+ // BETWEEN
+ "BETWEEN",
+ // CASE
+ "CASE",
+ // CAST
+ "CAST",
+ // CHECK
+ "CHECK",
+ // CONSTRAINT
+ "CONSTRAINT",
+ // CROSS
+ "CROSS",
+ // CURRENT_CATALOG
+ "CURRENT_CATALOG",
+ // CURRENT_DATE
+ "CURRENT_DATE",
+ // CURRENT_PATH
+ "CURRENT_PATH",
+ // CURRENT_ROLE
+ "CURRENT_ROLE",
+ // CURRENT_SCHEMA
+ "CURRENT_SCHEMA",
+ // CURRENT_TIME
+ "CURRENT_TIME",
+ // CURRENT_TIMESTAMP
+ "CURRENT_TIMESTAMP",
+ // CURRENT_USER
+ "CURRENT_USER",
+ // DAY
+ "DAY",
+ // DEFAULT
+ "DEFAULT",
+ // DISTINCT
+ "DISTINCT",
+ // ELSE
+ "ELSE",
+ // END
+ "END",
+ // EXCEPT
+ "EXCEPT",
+ // EXISTS
+ "EXISTS",
+ // FALSE
+ "FALSE",
+ // FETCH
+ "FETCH",
+ // FOR
+ "FOR",
+ // FOREIGN
+ "FOREIGN",
+ // FROM
+ "FROM",
+ // FULL
+ "FULL",
+ // GROUP
+ "GROUP",
+ // HAVING
+ "HAVING",
+ // HOUR
+ "HOUR",
+ // IF
+ "IF",
+ // IN
+ "IN",
+ // INNER
+ "INNER",
+ // INTERSECT
+ "INTERSECT",
+ // INTERVAL
+ "INTERVAL",
+ // IS
+ "IS",
+ // JOIN
+ "JOIN",
+ // KEY
+ "KEY",
+ // LEFT
+ "LEFT",
+ // LIKE
+ "LIKE",
+ // LIMIT
+ "LIMIT",
+ // LOCALTIME
+ "LOCALTIME",
+ // LOCALTIMESTAMP
+ "LOCALTIMESTAMP",
+ // MINUS
+ "MINUS",
+ // MINUTE
+ "MINUTE",
+ // MONTH
+ "MONTH",
+ // NATURAL
+ "NATURAL",
+ // NOT
+ "NOT",
+ // NULL
+ "NULL",
+ // OFFSET
+ "OFFSET",
+ // ON
+ "ON",
+ // OR
+ "OR",
+ // ORDER
+ "ORDER",
+ // PRIMARY
+ "PRIMARY",
+ // QUALIFY
+ "QUALIFY",
+ // RIGHT
+ "RIGHT",
+ // ROW
+ "ROW",
+ // ROWNUM
+ "ROWNUM",
+ // SECOND
+ "SECOND",
+ // SELECT
+ "SELECT",
+ // SESSION_USER
+ "SESSION_USER",
+ // SET
+ "SET",
+ // SOME
+ "SOME",
+ // SYMMETRIC
+ "SYMMETRIC",
+ // SYSTEM_USER
+ "SYSTEM_USER",
+ // TABLE
+ "TABLE",
+ // TO
+ "TO",
+ // TRUE
+ "TRUE",
+ // UESCAPE
+ "UESCAPE",
+ // UNION
+ "UNION",
+ // UNIQUE
+ "UNIQUE",
+ // UNKNOWN
+ "UNKNOWN",
+ // USER
+ "USER",
+ // USING
+ "USING",
+ // VALUE
+ "VALUE",
+ // VALUES
+ "VALUES",
+ // WHEN
+ "WHEN",
+ // WHERE
+ "WHERE",
+ // WINDOW
+ "WINDOW",
+ // WITH
+ "WITH",
+ // YEAR
+ "YEAR",
+ // _ROWID_
+ "_ROWID_",
+ // PARAMETER
+ "?",
+ // END_OF_INPUT
+ null,
+ // LITERAL
+ null,
+ // EQUAL
+ "=",
+ // BIGGER_EQUAL
+ ">=",
+ // BIGGER
+ ">",
+ // SMALLER
+ "<",
+ // SMALLER_EQUAL
+ "<=",
+ // NOT_EQUAL
+ "<>",
+ // AT
+ "@",
+ // MINUS_SIGN
+ "-",
+ // PLUS_SIGN
+ "+",
+ // CONCATENATION
+ "||",
+ // OPEN_PAREN
+ "(",
+ // CLOSE_PAREN
+ ")",
+ // SPATIAL_INTERSECTS
+ "&&",
+ // ASTERISK
+ "*",
+ // COMMA
+ ",",
+ // DOT
+ ".",
+ // OPEN_BRACE
+ "{",
+ // CLOSE_BRACE
+ "}",
+ // SLASH
+ "/",
+ // PERCENT
+ "%",
+ // SEMICOLON
+ ";",
+ // COLON
+ ":",
+ // OPEN_BRACKET
+ "[",
+ // CLOSE_BRACKET
+ "]",
+ // TILDE
+ "~",
+ // COLON_COLON
+ "::",
+ // COLON_EQ
+ ":=",
+ // NOT_TILDE
+ "!~",
+ // End
+ };
+
+ static class IdentifierToken extends Token {
+
+ private String identifier;
+
+ private final boolean quoted;
+
+ private boolean unicode;
+
+ IdentifierToken(int start, String identifier, boolean quoted, boolean unicode) {
+ super(start);
+ this.identifier = identifier;
+ this.quoted = quoted;
+ this.unicode = unicode;
+ }
+
+ @Override
+ int tokenType() {
+ return IDENTIFIER;
+ }
+
+ @Override
+ String asIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ boolean isQuoted() {
+ return quoted;
+ }
+
+ @Override
+ boolean needsUnicodeConversion() {
+ return unicode;
+ }
+
+ @Override
+ void convertUnicode(int uescape) {
+ if (unicode) {
+ identifier = StringUtils.decodeUnicodeStringSQL(identifier, uescape);
+ unicode = false;
+ } else {
+ throw DbException.getInternalError();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return quoted ? StringUtils.quoteIdentifier(identifier) : identifier;
+ }
+
+ }
+
+ static final class KeywordToken extends Token {
+
+ private final int type;
+
+ KeywordToken(int start, int type) {
+ super(start);
+ this.type = type;
+ }
+
+ @Override
+ int tokenType() {
+ return type;
+ }
+
+ @Override
+ String asIdentifier() {
+ return TOKENS[type];
+ }
+
+ @Override
+ public String toString() {
+ return TOKENS[type];
+ }
+
+ }
+
+ static final class KeywordOrIdentifierToken extends Token {
+
+ private final int type;
+
+ private final String identifier;
+
+ KeywordOrIdentifierToken(int start, int type, String identifier) {
+ super(start);
+ this.type = type;
+ this.identifier = identifier;
+ }
+
+ @Override
+ int tokenType() {
+ return type;
+ }
+
+ @Override
+ String asIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public String toString() {
+ return identifier;
+ }
+
+ }
+
+ static abstract class LiteralToken extends Token {
+
+ Value value;
+
+ LiteralToken(int start) {
+ super(start);
+ }
+
+ @Override
+ final int tokenType() {
+ return LITERAL;
+ }
+
+ @Override
+ public final String toString() {
+ return value(null).getTraceSQL();
+ }
+
+ }
+
+ static final class BinaryStringToken extends LiteralToken {
+
+ private final byte[] string;
+
+ BinaryStringToken(int start, byte[] string) {
+ super(start);
+ this.string = string;
+ }
+
+ @Override
+ Value value(CastDataProvider provider) {
+ if (value == null) {
+ value = ValueVarbinary.getNoCopy(string);
+ }
+ return value;
+ }
+
+ }
+
+ static final class CharacterStringToken extends LiteralToken {
+
+ String string;
+
+ private boolean unicode;
+
+ CharacterStringToken(int start, String string, boolean unicode) {
+ super(start);
+ this.string = string;
+ this.unicode = unicode;
+ }
+
+ @Override
+ Value value(CastDataProvider provider) {
+ if (value == null) {
+ value = ValueVarchar.get(string, provider);
+ }
+ return value;
+ }
+
+ @Override
+ boolean needsUnicodeConversion() {
+ return unicode;
+ }
+
+ @Override
+ void convertUnicode(int uescape) {
+ if (unicode) {
+ string = StringUtils.decodeUnicodeStringSQL(string, uescape);
+ unicode = false;
+ } else {
+ throw DbException.getInternalError();
+ }
+ }
+
+ }
+
+ static final class IntegerToken extends LiteralToken {
+
+ private final int number;
+
+ IntegerToken(int start, int number) {
+ super(start);
+ this.number = number;
+ }
+
+ @Override
+ Value value(CastDataProvider provider) {
+ if (value == null) {
+ value = ValueInteger.get(number);
+ }
+ return value;
+ }
+
+ }
+
+ static final class BigintToken extends LiteralToken {
+
+ private final long number;
+
+ BigintToken(int start, long number) {
+ super(start);
+ this.number = number;
+ }
+
+ @Override
+ Value value(CastDataProvider provider) {
+ if (value == null) {
+ value = ValueBigint.get(number);
+ }
+ return value;
+ }
+
+ }
+
+ static final class ValueToken extends LiteralToken {
+
+ ValueToken(int start, Value value) {
+ super(start);
+ this.value = value;
+ }
+
+ @Override
+ Value value(CastDataProvider provider) {
+ return value;
+ }
+
+ }
+
+ static final class ParameterToken extends Token {
+
+ private final int index;
+
+ ParameterToken(int start, int index) {
+ super(start);
+ this.index = index;
+ }
+
+ @Override
+ int tokenType() {
+ return PARAMETER;
+ }
+
+ @Override
+ String asIdentifier() {
+ return "?";
+ }
+
+ int index() {
+ return index;
+ }
+
+ @Override
+ public String toString() {
+ return index == 0 ? "?" : "?" + index;
+ }
+
+ }
+
+ static final class EndOfInputToken extends Token {
+
+ EndOfInputToken(int start) {
+ super(start);
+ }
+
+ @Override
+ int tokenType() {
+ return END_OF_INPUT;
+ }
+
+ }
+
+ private int start;
+
+ Token(int start) {
+ this.start = start;
+ }
+
+ final int start() {
+ return start;
+ }
+
+ final void setStart(int offset) {
+ start = offset;
+ }
+
+ final void subtractFromStart(int offset) {
+ start -= offset;
+ }
+
+ abstract int tokenType();
+
+ String asIdentifier() {
+ return null;
+ }
+
+ boolean isQuoted() {
+ return false;
+ }
+
+ Value value(CastDataProvider provider) {
+ return null;
+ }
+
+ boolean needsUnicodeConversion() {
+ return false;
+ }
+
+ void convertUnicode(int uescape) {
+ throw DbException.getInternalError();
+ }
+
+ @Override
+ protected Token clone() {
+ try {
+ return (Token) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw DbException.getInternalError();
+ }
+ }
+
+}
diff --git a/h2/src/main/org/h2/command/Tokenizer.java b/h2/src/main/org/h2/command/Tokenizer.java
new file mode 100644
index 0000000000..dd35335097
--- /dev/null
+++ b/h2/src/main/org/h2/command/Tokenizer.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
+ * and the EPL 1.0 (https://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.h2.command;
+
+import static org.h2.command.Token.ASTERISK;
+import static org.h2.command.Token.AT;
+import static org.h2.command.Token.BIGGER;
+import static org.h2.command.Token.BIGGER_EQUAL;
+import static org.h2.command.Token.CLOSE_BRACE;
+import static org.h2.command.Token.CLOSE_BRACKET;
+import static org.h2.command.Token.CLOSE_PAREN;
+import static org.h2.command.Token.COLON;
+import static org.h2.command.Token.COLON_COLON;
+import static org.h2.command.Token.COLON_EQ;
+import static org.h2.command.Token.COMMA;
+import static org.h2.command.Token.CONCATENATION;
+import static org.h2.command.Token.DOT;
+import static org.h2.command.Token.EQUAL;
+import static org.h2.command.Token.MINUS_SIGN;
+import static org.h2.command.Token.NOT_EQUAL;
+import static org.h2.command.Token.NOT_TILDE;
+import static org.h2.command.Token.OPEN_BRACE;
+import static org.h2.command.Token.OPEN_BRACKET;
+import static org.h2.command.Token.OPEN_PAREN;
+import static org.h2.command.Token.PERCENT;
+import static org.h2.command.Token.PLUS_SIGN;
+import static org.h2.command.Token.SEMICOLON;
+import static org.h2.command.Token.SLASH;
+import static org.h2.command.Token.SMALLER;
+import static org.h2.command.Token.SMALLER_EQUAL;
+import static org.h2.command.Token.SPATIAL_INTERSECTS;
+import static org.h2.command.Token.TILDE;
+import static org.h2.util.ParserUtil.IDENTIFIER;
+import static org.h2.util.ParserUtil.LIMIT;
+import static org.h2.util.ParserUtil.MINUS;
+import static org.h2.util.ParserUtil.UESCAPE;
+
+import java.io.ByteArrayOutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.ListIterator;
+
+import org.h2.api.ErrorCode;
+import org.h2.engine.CastDataProvider;
+import org.h2.message.DbException;
+import org.h2.util.ParserUtil;
+import org.h2.util.StringUtils;
+import org.h2.value.ValueBigint;
+import org.h2.value.ValueDecfloat;
+import org.h2.value.ValueNumeric;
+
+/**
+ * Tokenizer.
+ */
+public final class Tokenizer {
+
+ private Tokenizer() {
+ }
+
+ static ArrayList tokenize(String sql, CastDataProvider provider, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, boolean stopOnCloseParen) {
+ ArrayList tokens = new ArrayList<>();
+ int end = sql.length() - 1;
+ boolean foundUnicode = false;
+ loop: for (int i = 0; i <= end;) {
+ int tokenStart = i;
+ char c = sql.charAt(i);
+ Token token;
+ switch (c) {
+ case '!':
+ if (i < end) {
+ char c2 = sql.charAt(++i);
+ if (c2 == '=') {
+ token = new Token.KeywordToken(tokenStart, NOT_EQUAL);
+ break;
+ }
+ if (c2 == '~') {
+ token = new Token.KeywordToken(tokenStart, NOT_TILDE);
+ break;
+ }
+ }
+ throw DbException.getSyntaxError(sql, tokenStart);
+ case '"':
+ case '`':
+ i = readQuotedIdentifier(sql, end, identifiersToUpper, identifiersToLower, tokenStart, i, c, false,
+ tokens);
+ continue loop;
+ case '#':
+ if (provider.getMode().supportPoundSymbolForColumnNames) {
+ i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i, c,
+ tokens);
+ continue loop;
+ }
+ throw DbException.getSyntaxError(sql, tokenStart);
+ case '$':
+ if (i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 == '$') {
+ i += 2;
+ int stringEnd = sql.indexOf("$$", i);
+ if (stringEnd < 0) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ token = new Token.CharacterStringToken(tokenStart, sql.substring(i, stringEnd), false);
+ i = stringEnd + 1;
+ } else {
+ i = parseParameterIndex(sql, end, i, tokens);
+ continue loop;
+ }
+ } else {
+ token = new Token.ParameterToken(tokenStart, 0);
+ }
+ break;
+ case '%':
+ token = new Token.KeywordToken(tokenStart, PERCENT);
+ break;
+ case '&':
+ if (i < end && sql.charAt(i + 1) == '&') {
+ i++;
+ token = new Token.KeywordToken(tokenStart, SPATIAL_INTERSECTS);
+ break;
+ }
+ throw DbException.getSyntaxError(sql, tokenStart);
+ case '\'':
+ i = readCharacterString(sql, provider, tokenStart, end, i, false, tokens);
+ continue loop;
+ case '(':
+ token = new Token.KeywordToken(tokenStart, OPEN_PAREN);
+ break;
+ case ')':
+ token = new Token.KeywordToken(tokenStart, CLOSE_PAREN);
+ if (stopOnCloseParen) {
+ tokens.add(token);
+ end = skipWhitespace(sql, end, i + 1) - 1;
+ break loop;
+ }
+ break;
+ case '*':
+ token = new Token.KeywordToken(tokenStart, ASTERISK);
+ break;
+ case '+':
+ token = new Token.KeywordToken(tokenStart, PLUS_SIGN);
+ break;
+ case ',':
+ token = new Token.KeywordToken(tokenStart, COMMA);
+ break;
+ case '-':
+ if (i < end && sql.charAt(i + 1) == '-') {
+ i = skipSimpleComment(sql, end, i);
+ continue loop;
+ } else {
+ token = new Token.KeywordToken(tokenStart, MINUS_SIGN);
+ }
+ break;
+ case '.':
+ if (i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 >= '0' && c2 <= '9') {
+ i = readNumeric(sql, tokenStart, end, i + 1, c2, false, false, tokens);
+ continue loop;
+ }
+ }
+ token = new Token.KeywordToken(tokenStart, DOT);
+ break;
+ case '/':
+ if (i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 == '*') {
+ i = skipBracketedComment(sql, tokenStart, end, i);
+ continue loop;
+ } else if (c2 == '/') {
+ i = skipSimpleComment(sql, end, i);
+ continue loop;
+ }
+ }
+ token = new Token.KeywordToken(tokenStart, SLASH);
+ break;
+ case '0':
+ if (i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 == 'X' || c2 == 'x') {
+ i = readHexNumber(sql, provider, tokenStart, end, i + 2, tokens);
+ continue loop;
+ }
+ }
+ //$FALL-THROUGH$
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ i = readNumeric(sql, tokenStart, end, i + 1, c, tokens);
+ continue loop;
+ case ':':
+ if (i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 == ':') {
+ i++;
+ token = new Token.KeywordToken(tokenStart, COLON_COLON);
+ break;
+ } else if (c2 == '=') {
+ i++;
+ token = new Token.KeywordToken(tokenStart, COLON_EQ);
+ break;
+ }
+ }
+ token = new Token.KeywordToken(tokenStart, COLON);
+ break;
+ case ';':
+ token = new Token.KeywordToken(tokenStart, SEMICOLON);
+ break;
+ case '<':
+ if (i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 == '=') {
+ i++;
+ token = new Token.KeywordToken(tokenStart, SMALLER_EQUAL);
+ break;
+ }
+ if (c2 == '>') {
+ i++;
+ token = new Token.KeywordToken(tokenStart, NOT_EQUAL);
+ break;
+ }
+ }
+ token = new Token.KeywordToken(tokenStart, SMALLER);
+ break;
+ case '=':
+ token = new Token.KeywordToken(tokenStart, EQUAL);
+ break;
+ case '>':
+ if (i < end && sql.charAt(i + 1) == '=') {
+ i++;
+ token = new Token.KeywordToken(tokenStart, BIGGER_EQUAL);
+ break;
+ }
+ token = new Token.KeywordToken(tokenStart, BIGGER);
+ break;
+ case '?':
+ if (i + 1 < end && sql.charAt(i + 1) == '?') {
+ char c3 = sql.charAt(i + 2);
+ if (c3 == '(') {
+ i += 2;
+ token = new Token.KeywordToken(tokenStart, OPEN_BRACKET);
+ break;
+ }
+ if (c3 == ')') {
+ i += 2;
+ token = new Token.KeywordToken(tokenStart, CLOSE_BRACKET);
+ break;
+ }
+ }
+ i = parseParameterIndex(sql, end, i, tokens);
+ continue loop;
+ case '@':
+ token = new Token.KeywordToken(tokenStart, AT);
+ break;
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'V':
+ case 'W':
+ case 'Y':
+ case 'Z':
+ case '_':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'v':
+ case 'w':
+ case 'y':
+ case 'z':
+ i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
+ tokenStart, i, c, tokens);
+ continue loop;
+ case 'N':
+ case 'n':
+ if (i < end && sql.charAt(i + 1) == '\'') {
+ i = readCharacterString(sql, provider, tokenStart, end, i + 1, false, tokens);
+ continue loop;
+ }
+ i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
+ tokenStart, i, c, tokens);
+ continue loop;
+ case 'X':
+ case 'x':
+ if (i < end && sql.charAt(i + 1) == '\'') {
+ i = readBinaryString(sql, provider, tokenStart, end, i + 1, tokens);
+ continue loop;
+ }
+ i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
+ tokenStart, i, c, tokens);
+ continue loop;
+ case 'U':
+ case 'u':
+ if (i + 1 < end && sql.charAt(i + 1) == '&') {
+ char c3 = sql.charAt(i + 2);
+ if (c3 == '"') {
+ i = readQuotedIdentifier(sql, end, identifiersToUpper, identifiersToLower, tokenStart, //
+ i + 2, '"', true, tokens);
+ foundUnicode = true;
+ continue loop;
+ } else if (c3 == '\'') {
+ i = readCharacterString(sql, provider, tokenStart, end, i + 2, true, tokens);
+ foundUnicode = true;
+ continue loop;
+ }
+ }
+ i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
+ tokenStart, i, c, tokens);
+ continue loop;
+ case '[':
+ if (provider.getMode().squareBracketQuotedNames) {
+ int identifierEnd = sql.indexOf(']', ++i);
+ if (identifierEnd < 0) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ token = new Token.IdentifierToken(tokenStart, sql.substring(i, identifierEnd), true, false);
+ i = identifierEnd;
+ } else {
+ token = new Token.KeywordToken(tokenStart, OPEN_BRACKET);
+ }
+ break;
+ case ']':
+ token = new Token.KeywordToken(tokenStart, CLOSE_BRACKET);
+ break;
+ case '{':
+ token = new Token.KeywordToken(tokenStart, OPEN_BRACE);
+ break;
+ case '|':
+ if (i < end && sql.charAt(++i) == '|') {
+ token = new Token.KeywordToken(tokenStart, CONCATENATION);
+ break;
+ }
+ throw DbException.getSyntaxError(sql, tokenStart);
+ case '}':
+ token = new Token.KeywordToken(tokenStart, CLOSE_BRACE);
+ break;
+ case '~':
+ token = new Token.KeywordToken(tokenStart, TILDE);
+ break;
+ default:
+ if (c <= ' ') {
+ i++;
+ continue loop;
+ } else {
+ int cp = Character.isHighSurrogate(c) ? sql.codePointAt(i++) : c;
+ if (Character.isSpaceChar(cp)) {
+ continue loop;
+ }
+ if (Character.isJavaIdentifierStart(cp)) {
+ i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i,
+ cp, tokens);
+ continue loop;
+ }
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ }
+ tokens.add(token);
+ i++;
+ }
+ if (foundUnicode) {
+ processUescape(sql, tokens);
+ }
+ tokens.add(new Token.EndOfInputToken(end + 1));
+ return tokens;
+ }
+
+ private static int readIdentifier(String sql, CastDataProvider provider, int end, boolean identifiersToUpper, //
+ boolean identifiersToLower, int tokenStart, int i, int cp, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i, cp);
+ tokens.add(new Token.IdentifierToken(tokenStart,
+ extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), false, false));
+ return endIndex;
+ }
+
+ private static int readIdentifierOrKeyword(String sql, CastDataProvider provider, int end,
+ boolean identifiersToUpper, boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, char c,
+ ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i, c);
+ int type = ParserUtil.getTokenType(sql, true, tokenStart, endIndex - tokenStart, false);
+ Token token;
+ boolean keyword;
+ switch (type) {
+ case IDENTIFIER:
+ keyword = false;
+ break;
+ case LIMIT:
+ keyword = provider.getMode().limit;
+ break;
+ case MINUS:
+ keyword = provider.getMode().minusIsExcept;
+ break;
+ default:
+ keyword = true;
+ }
+ if (!keyword) {
+ token = new Token.IdentifierToken(tokenStart,
+ extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), false, false);
+ } else if (nonKeywords != null && nonKeywords.get(type)) {
+ token = new Token.KeywordOrIdentifierToken(tokenStart, type,
+ extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex));
+ } else {
+ token = new Token.KeywordToken(tokenStart, type);
+ }
+ tokens.add(token);
+ return endIndex;
+ }
+
+ private static int findIdentifierEnd(String sql, CastDataProvider provider, int end, int i, int cp) {
+ int next = i;
+ for (;; i = next) {
+ next += Character.charCount(cp);
+ if (next > end || (!Character.isJavaIdentifierPart(cp = sql.codePointAt(next))
+ && (cp != '#' || !provider.getMode().supportPoundSymbolForColumnNames))) {
+ break;
+ }
+ }
+ return next;
+ }
+
+ private static String extractIdentifier(String sql, boolean identifiersToUpper, boolean identifiersToLower,
+ int beginIndex, int endIndex) {
+ return convertCase(identifiersToUpper, identifiersToLower, sql.substring(beginIndex, endIndex));
+ }
+
+ private static int readQuotedIdentifier(String sql, int end, boolean identifiersToUpper, //
+ boolean identifiersToLower, int tokenStart, int i, char c, boolean unicode, ArrayList tokens) {
+ int identifierEnd = sql.indexOf(c, ++i);
+ if (identifierEnd < 0) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ String s = sql.substring(i, identifierEnd);
+ i = identifierEnd + 1;
+ if (i <= end && sql.charAt(i) == c) {
+ StringBuilder builder = new StringBuilder(s);
+ do {
+ identifierEnd = sql.indexOf(c, i + 1);
+ if (identifierEnd < 0) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ builder.append(sql, i, identifierEnd);
+ i = identifierEnd + 1;
+ } while (i <= end && sql.charAt(i) == c);
+ s = builder.toString();
+ }
+ if (c == '`') {
+ s = convertCase(identifiersToUpper, identifiersToLower, s);
+ }
+ tokens.add(new Token.IdentifierToken(tokenStart, s, true, unicode));
+ return i;
+ }
+
+ private static String convertCase(boolean identifiersToUpper, boolean identifiersToLower, String s) {
+ if (identifiersToUpper) {
+ s = StringUtils.toUpperEnglish(s);
+ } else if (identifiersToLower) {
+ s = StringUtils.toLowerEnglish(s);
+ }
+ return s;
+ }
+
+ private static int readBinaryString(String sql, CastDataProvider provider, int tokenStart, int end, int i,
+ ArrayList tokens) {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ int stringEnd;
+ do {
+ stringEnd = sql.indexOf('\'', ++i);
+ if (stringEnd < 0 || stringEnd < end && sql.charAt(stringEnd + 1) == '\'') {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ StringUtils.convertHexWithSpacesToBytes(result, sql, i, stringEnd);
+ i = skipWhitespace(sql, end, stringEnd + 1);
+ } while (i <= end && sql.charAt(i) == '\'');
+ tokens.add(new Token.BinaryStringToken(tokenStart, result.toByteArray()));
+ return i;
+ }
+
+ private static int readCharacterString(String sql, CastDataProvider provider, int tokenStart, int end, int i,
+ boolean unicode, ArrayList tokens) {
+ String s = null;
+ StringBuilder builder = null;
+ int stringEnd;
+ do {
+ stringEnd = sql.indexOf('\'', ++i);
+ if (stringEnd < 0) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ if (s == null) {
+ s = sql.substring(i, stringEnd);
+ } else {
+ if (builder == null) {
+ builder = new StringBuilder(s);
+ }
+ builder.append(sql, i, stringEnd);
+ }
+ i = stringEnd + 1;
+ if (i <= end && sql.charAt(i) == '\'') {
+ if (builder == null) {
+ builder = new StringBuilder(s);
+ }
+ do {
+ stringEnd = sql.indexOf('\'', i + 1);
+ if (stringEnd < 0) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ builder.append(sql, i, stringEnd);
+ i = stringEnd + 1;
+ } while (i <= end && sql.charAt(i) == '\'');
+ }
+ i = skipWhitespace(sql, end, i);
+ } while (i <= end && sql.charAt(i) == '\'');
+ if (builder != null) {
+ s = builder.toString();
+ }
+ tokens.add(new Token.CharacterStringToken(tokenStart, s, unicode));
+ return i;
+ }
+
+ private static int skipWhitespace(String sql, int end, int i) {
+ while (i <= end) {
+ int cp = sql.codePointAt(i);
+ if (!Character.isWhitespace(cp)) {
+ if (cp == '/' && i < end) {
+ char c2 = sql.charAt(i + 1);
+ if (c2 == '*') {
+ i = skipBracketedComment(sql, i, end, i);
+ continue;
+ } else if (c2 == '/') {
+ i = skipSimpleComment(sql, end, i);
+ continue;
+ }
+ }
+ break;
+ }
+ i += Character.charCount(cp);
+ }
+ return i;
+ }
+
+ private static int readHexNumber(String sql, CastDataProvider provider, int tokenStart, int end, int i,
+ ArrayList tokens) {
+ if (provider.getMode().zeroExLiteralsAreBinaryStrings) {
+ int start = i;
+ for (char c; i <= end
+ && (((c = sql.charAt(i)) >= '0' && c <= '9') || ((c &= 0xffdf) >= 'A' && c <= 'F'));) {
+ i++;
+ }
+ if (i <= end && Character.isJavaIdentifierPart(sql.codePointAt(i))) {
+ throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, sql.substring(start, i + 1));
+ }
+ tokens.add(new Token.BinaryStringToken(start, StringUtils.convertHexToBytes(sql.substring(start, i))));
+ return i;
+ } else {
+ if (i > end) {
+ throw DbException.getSyntaxError(sql, tokenStart, "Hex number");
+ }
+ int start = i;
+ long number = 0;
+ char c;
+ do {
+ c = sql.charAt(i);
+ if (c >= '0' && c <= '9') {
+ number = (number << 4) + c - '0';
+ // Convert a-z to A-Z
+ } else if ((c &= 0xffdf) >= 'A' && c <= 'F') {
+ number = (number << 4) + c - ('A' - 10);
+ } else if (i == start) {
+ throw DbException.getSyntaxError(sql, tokenStart, "Hex number");
+ } else {
+ break;
+ }
+ if (number > Integer.MAX_VALUE) {
+ while (++i <= end
+ && (((c = sql.charAt(i)) >= '0' && c <= '9') || ((c &= 0xffdf) >= 'A' && c <= 'F'))) {
+ }
+ return finishBigInteger(sql, tokenStart, end, i, start, i <= end && c == 'L', 16, tokens);
+ }
+ } while (++i <= end);
+
+ boolean bigint = i <= end && c == 'L';
+ if (bigint) {
+ i++;
+ }
+ if (i <= end && Character.isJavaIdentifierPart(sql.codePointAt(i))) {
+ throw DbException.getSyntaxError(sql, tokenStart, "Hex number");
+ }
+ tokens.add(bigint ? new Token.BigintToken(start, number) : new Token.IntegerToken(start, (int) number));
+ return i;
+ }
+ }
+
+ private static int readNumeric(String sql, int tokenStart, int end, int i, char c, ArrayList tokens) {
+ long number = c - '0';
+ for (; i <= end; i++) {
+ c = sql.charAt(i);
+ if (c < '0' || c > '9') {
+ switch (c) {
+ case '.':
+ return readNumeric(sql, tokenStart, end, i, c, false, false, tokens);
+ case 'E':
+ case 'e':
+ return readNumeric(sql, tokenStart, end, i, c, false, true, tokens);
+ case 'L':
+ case 'l':
+ return finishBigInteger(sql, tokenStart, end, i, tokenStart, true, 10, tokens);
+ }
+ break;
+ }
+ number = number * 10 + (c - '0');
+ if (number > Integer.MAX_VALUE) {
+ return readNumeric(sql, tokenStart, end, i, c, true, false, tokens);
+ }
+ }
+ tokens.add(new Token.IntegerToken(tokenStart, (int) number));
+ return i;
+ }
+
+ private static int readNumeric(String sql, int tokenStart, int end, int i, char c, boolean integer,
+ boolean approximate, ArrayList tokens) {
+ if (!approximate) {
+ while (++i <= end) {
+ c = sql.charAt(i);
+ if (c == '.') {
+ integer = false;
+ } else if (c < '0' || c > '9') {
+ break;
+ }
+ }
+ }
+ if (i <= end && (c == 'E' || c == 'e')) {
+ integer = false;
+ approximate = true;
+ if (i == end) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ c = sql.charAt(++i);
+ if (c == '+' || c == '-') {
+ if (i == end) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ c = sql.charAt(++i);
+ }
+ if (c < '0' || c > '9') {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ while (++i <= end && (c = sql.charAt(i)) >= '0' && c <= '9') {
+ // go until the first non-number
+ }
+ }
+ if (integer) {
+ return finishBigInteger(sql, tokenStart, end, i, tokenStart, i < end && c == 'L' || c == 'l', 10, tokens);
+ }
+ BigDecimal bd;
+ String string = sql.substring(tokenStart, i);
+ try {
+ bd = new BigDecimal(string);
+ } catch (NumberFormatException e) {
+ throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, string);
+ }
+ tokens.add(new Token.ValueToken(tokenStart, approximate ? ValueDecfloat.get(bd) : ValueNumeric.get(bd)));
+ return i;
+ }
+
+ private static int finishBigInteger(String sql, int tokenStart, int end, int i, int start, boolean asBigint,
+ int radix, ArrayList tokens) {
+ int endIndex = i;
+ if (asBigint) {
+ i++;
+ }
+ if (radix == 16 && i <= end && Character.isJavaIdentifierPart(sql.codePointAt(i))) {
+ throw DbException.getSyntaxError(sql, tokenStart, "Hex number");
+ }
+ BigInteger bigInteger = new BigInteger(sql.substring(start, endIndex), radix);
+ Token token;
+ if (bigInteger.compareTo(ValueBigint.MAX_BI) > 0) {
+ if (asBigint) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ token = new Token.ValueToken(tokenStart, ValueNumeric.get(bigInteger));
+ } else {
+ token = new Token.BigintToken(start, bigInteger.longValue());
+ }
+ tokens.add(token);
+ return i;
+ }
+
+ private static int skipBracketedComment(String sql, int tokenStart, int end, int i) {
+ i += 2;
+ for (int level = 1; level > 0;) {
+ for (;;) {
+ if (i >= end) {
+ throw DbException.getSyntaxError(sql, tokenStart);
+ }
+ char c = sql.charAt(i++);
+ if (c == '*') {
+ if (sql.charAt(i) == '/') {
+ level--;
+ i++;
+ break;
+ }
+ } else if (c == '/' && sql.charAt(i) == '*') {
+ level++;
+ i++;
+ }
+ }
+ }
+ return i;
+ }
+
+ private static int skipSimpleComment(String sql, int end, int i) {
+ i += 2;
+ for (char c; i <= end && (c = sql.charAt(i)) != '\n' && c != '\r'; i++) {
+ //
+ }
+ return i;
+ }
+
+ private static int parseParameterIndex(String sql, int end, int i, ArrayList tokens) {
+ int tokenStart = i;
+ long number = 0;
+ for (char c; ++i <= end && (c = sql.charAt(i)) >= '0' && c <= '9';) {
+ number = number * 10 + (c - '0');
+ if (number > Integer.MAX_VALUE) {
+ throw DbException.getInvalidValueException("parameter index", number);
+ }
+ }
+ if (i > tokenStart + 1 && number == 0) {
+ throw DbException.getInvalidValueException("parameter index", number);
+ }
+ tokens.add(new Token.ParameterToken(tokenStart, (int) number));
+ return i;
+ }
+
+ private static void processUescape(String sql, ArrayList tokens) {
+ ListIterator i = tokens.listIterator();
+ while (i.hasNext()) {
+ Token token = i.next();
+ if (token.needsUnicodeConversion()) {
+ int uescape = '\\';
+ condition: if (i.hasNext()) {
+ Token t2 = i.next();
+ if (t2.tokenType() == UESCAPE) {
+ i.remove();
+ if (i.hasNext()) {
+ Token t3 = i.next();
+ i.remove();
+ if (t3 instanceof Token.CharacterStringToken) {
+ String s = ((Token.CharacterStringToken) t3).string;
+ if (s.codePointCount(0, s.length()) == 1) {
+ int escape = s.codePointAt(0);
+ if (!Character.isWhitespace(escape) && (escape < '0' || escape > '9')
+ && (escape < 'A' || escape > 'F') && (escape < 'a' || escape > 'f')) {
+ switch (escape) {
+ default:
+ uescape = escape;
+ break condition;
+ case '"':
+ case '\'':
+ case '+':
+ }
+ }
+ }
+ }
+ }
+ throw DbException.getSyntaxError(sql, t2.start() + 7, "''");
+ }
+ }
+ token.convertUnicode(uescape);
+ }
+ }
+ }
+
+}
diff --git a/h2/src/main/org/h2/util/StringUtils.java b/h2/src/main/org/h2/util/StringUtils.java
index ca8fe37d91..85bca6b51f 100644
--- a/h2/src/main/org/h2/util/StringUtils.java
+++ b/h2/src/main/org/h2/util/StringUtils.java
@@ -1107,7 +1107,7 @@ public static byte[] convertHexToBytes(String s) {
* @param end the end index, exclusive
* @return the specified output stream or a new output stream
*/
- public static ByteArrayOutputStream convertHexWithSpacesToBytes(ByteArrayOutputStream baos, char[] s, int start,
+ public static ByteArrayOutputStream convertHexWithSpacesToBytes(ByteArrayOutputStream baos, String s, int start,
int end) {
if (baos == null) {
baos = new ByteArrayOutputStream((end - start) >>> 1);
@@ -1121,7 +1121,7 @@ public static ByteArrayOutputStream convertHexWithSpacesToBytes(ByteArrayOutputS
if (i >= end) {
break loop;
}
- c1 = s[i++];
+ c1 = s.charAt(i++);
} while (c1 == ' ');
do {
if (i >= end) {
@@ -1130,7 +1130,7 @@ public static ByteArrayOutputStream convertHexWithSpacesToBytes(ByteArrayOutputS
}
throw getHexStringException(ErrorCode.HEX_STRING_ODD_1, s, start, end);
}
- c2 = s[i++];
+ c2 = s.charAt(i++);
} while (c2 == ' ');
int d = hex[c1] << 4 | hex[c2];
mask |= d;
@@ -1145,8 +1145,8 @@ public static ByteArrayOutputStream convertHexWithSpacesToBytes(ByteArrayOutputS
return baos;
}
- private static DbException getHexStringException(int code, char[] s, int start, int end) {
- return DbException.get(code, new String(s, start, end - start));
+ private static DbException getHexStringException(int code, String s, int start, int end) {
+ return DbException.get(code, s.substring(start, end));
}
/**
diff --git a/h2/src/test/org/h2/test/unit/TestKeywords.java b/h2/src/test/org/h2/test/unit/TestKeywords.java
index ab304f31ff..65430b6028 100644
--- a/h2/src/test/org/h2/test/unit/TestKeywords.java
+++ b/h2/src/test/org/h2/test/unit/TestKeywords.java
@@ -18,6 +18,8 @@
import java.util.TreeSet;
import org.h2.command.Parser;
+import org.h2.command.Token;
+import org.h2.command.Tokenizer;
import org.h2.message.DbException;
import org.h2.test.TestBase;
import org.h2.util.ParserUtil;
@@ -473,9 +475,17 @@ private enum TokenType {
set.addAll(SQL2016_RESERVED_WORDS);
ALL_RESEVED_WORDS = set;
HashMap tokens = new HashMap<>();
+ processClass(Parser.class, tokens);
+ processClass(ParserUtil.class, tokens);
+ processClass(Token.class, tokens);
+ processClass(Tokenizer.class, tokens);
+ TOKENS = tokens;
+ }
+
+ private static void processClass(Class> clazz, HashMap tokens) {
ClassReader r;
try {
- r = new ClassReader(Parser.class.getResourceAsStream("Parser.class"));
+ r = new ClassReader(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
} catch (IOException e) {
throw DbException.convert(e);
}
@@ -527,7 +537,6 @@ void add(Object value) {
tokens.put(s, type);
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
- TOKENS = tokens;
}
private static HashSet toSet(String[] array) {
From a252a5e1e7195de16b227f8ec21e0b319412d816 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Tue, 4 Jan 2022 15:00:54 +0800
Subject: [PATCH 34/73] Don't call read() from Parser.is*() methods
---
h2/src/main/org/h2/command/Parser.java | 96 +++++++++--------------
h2/src/main/org/h2/command/Tokenizer.java | 3 +-
2 files changed, 41 insertions(+), 58 deletions(-)
diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java
index f86cbb59cb..3cfd724354 100644
--- a/h2/src/main/org/h2/command/Parser.java
+++ b/h2/src/main/org/h2/command/Parser.java
@@ -1398,27 +1398,28 @@ private static Prepared prepare(SessionLocal s, String sql,
}
private boolean isDerivedTable() {
- int start = tokenIndex;
+ int offset = tokenIndex;
int level = 0;
- while (readIf(OPEN_PAREN)) {
+ while (tokens.get(offset).tokenType() == OPEN_PAREN) {
level++;
+ offset++;
}
- boolean query = isDirectQuery();
+ boolean query = isDirectQuery(offset);
s: if (query && level > 0) {
- read();
- if (!scanToCloseParen()) {
+ offset = scanToCloseParen(offset + 1);
+ if (offset < 0) {
query = false;
break s;
}
for (;;) {
- switch (currentTokenType) {
+ switch (tokens.get(offset).tokenType()) {
case SEMICOLON:
case END_OF_INPUT:
query = false;
break s;
case OPEN_PAREN:
- read();
- if (!scanToCloseParen()) {
+ offset = scanToCloseParen(offset + 1);
+ if (offset < 0) {
query = false;
break s;
}
@@ -1427,35 +1428,36 @@ private boolean isDerivedTable() {
if (--level == 0) {
break s;
}
- read();
+ offset++;
break;
case JOIN:
query = false;
break s;
default:
- read();
+ offset++;
}
}
}
- setTokenIndex(start);
return query;
}
private boolean isQuery() {
- int start = tokenIndex;
+ int offset = tokenIndex;
int level = 0;
- while (readIf(OPEN_PAREN)) {
+ while (tokens.get(offset).tokenType() == OPEN_PAREN) {
level++;
+ offset++;
}
- boolean query = isDirectQuery();
+ boolean query = isDirectQuery(offset);
s: if (query && level > 0) {
- read();
+ offset++;
do {
- if (!scanToCloseParen()) {
+ offset = scanToCloseParen(offset);
+ if (offset < 0) {
query = false;
break s;
}
- switch (currentTokenType) {
+ switch (tokens.get(offset).tokenType()) {
default:
query = false;
break s;
@@ -1473,51 +1475,45 @@ private boolean isQuery() {
}
} while (--level > 0);
}
- setTokenIndex(start);
return query;
}
- private boolean scanToCloseParen() {
+ private int scanToCloseParen(int offset) {
for (int level = 0;;) {
- switch (currentTokenType) {
+ switch (tokens.get(offset).tokenType()) {
case SEMICOLON:
case END_OF_INPUT:
- return false;
+ return -1;
case OPEN_PAREN:
level++;
break;
case CLOSE_PAREN:
if (--level < 0) {
- read();
- return true;
+ return offset + 1;
}
}
- read();
+ offset++;
}
}
private boolean isQueryQuick() {
- int start = tokenIndex;
- while (readIf(OPEN_PAREN)) {
- // need to read ahead, it could be a nested union:
- // ((select 1) union (select 1))
+ int offset = tokenIndex;
+ while (tokens.get(offset).tokenType() == OPEN_PAREN) {
+ offset++;
}
- boolean query = isDirectQuery();
- setTokenIndex(start);
- return query;
+ return isDirectQuery(offset);
}
- private boolean isDirectQuery() {
+ private boolean isDirectQuery(int offset) {
boolean query;
- switch (currentTokenType) {
+ switch (tokens.get(offset).tokenType()) {
case SELECT:
case VALUES:
case WITH:
query = true;
break;
case TABLE:
- read();
- query = !readIf(OPEN_PAREN);
+ query = tokens.get(offset + 1).tokenType() != OPEN_PAREN;
break;
default:
query = false;
@@ -2982,26 +2978,12 @@ private Select parseSelect(int start) {
* grouping set
*/
private boolean isOrdinaryGroupingSet() {
- int index = tokenIndex;
- int level = 1;
- loop: for (;;) {
- read();
- switch (currentTokenType) {
- case CLOSE_PAREN:
- if (--level <= 0) {
- break loop;
- }
- break;
- case OPEN_PAREN:
- level++;
- break;
- case END_OF_INPUT:
- addExpected(CLOSE_PAREN);
- throw getSyntaxError();
- }
+ int offset = scanToCloseParen(tokenIndex + 1);
+ if (offset < 0) {
+ // Try to parse as expression to get better syntax error
+ return false;
}
- read();
- switch (currentTokenType) {
+ switch (tokens.get(offset).tokenType()) {
// End of query
case CLOSE_PAREN:
case SEMICOLON:
@@ -3023,10 +3005,9 @@ private boolean isOrdinaryGroupingSet() {
case FETCH:
case LIMIT:
case FOR:
- setTokenIndex(index + 1);
+ setTokenIndex(tokenIndex + 1);
return true;
default:
- setTokenIndex(index);
return false;
}
}
@@ -5385,7 +5366,8 @@ && equalsToken("E", name)) {
read();
return v;
} else if (t == Value.VARBINARY && equalsToken("GEOMETRY", name)) {
- ValueExpression v = ValueExpression.get(ValueGeometry.getFromEWKB(token.value(session).getBytesNoCopy()));
+ ValueExpression v = ValueExpression
+ .get(ValueGeometry.getFromEWKB(token.value(session).getBytesNoCopy()));
read();
return v;
}
diff --git a/h2/src/main/org/h2/command/Tokenizer.java b/h2/src/main/org/h2/command/Tokenizer.java
index dd35335097..541a44451d 100644
--- a/h2/src/main/org/h2/command/Tokenizer.java
+++ b/h2/src/main/org/h2/command/Tokenizer.java
@@ -437,7 +437,8 @@ private static int readIdentifierOrKeyword(String sql, CastDataProvider provider
}
if (!keyword) {
token = new Token.IdentifierToken(tokenStart,
- extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), false, false);
+ extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), //
+ false, false);
} else if (nonKeywords != null && nonKeywords.get(type)) {
token = new Token.KeywordOrIdentifierToken(tokenStart, type,
extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex));
From 211d666560c9b1ecf1838675c314b7f449b87ab5 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Tue, 4 Jan 2022 16:40:57 +0800
Subject: [PATCH 35/73] Parse keywords directly in tokenizer
---
h2/src/main/org/h2/command/Tokenizer.java | 789 ++++++++++++++++++++--
1 file changed, 716 insertions(+), 73 deletions(-)
diff --git a/h2/src/main/org/h2/command/Tokenizer.java b/h2/src/main/org/h2/command/Tokenizer.java
index 541a44451d..540ca0ff84 100644
--- a/h2/src/main/org/h2/command/Tokenizer.java
+++ b/h2/src/main/org/h2/command/Tokenizer.java
@@ -33,10 +33,96 @@
import static org.h2.command.Token.SMALLER_EQUAL;
import static org.h2.command.Token.SPATIAL_INTERSECTS;
import static org.h2.command.Token.TILDE;
+import static org.h2.util.ParserUtil.ALL;
+import static org.h2.util.ParserUtil.AND;
+import static org.h2.util.ParserUtil.ANY;
+import static org.h2.util.ParserUtil.ARRAY;
+import static org.h2.util.ParserUtil.AS;
+import static org.h2.util.ParserUtil.ASYMMETRIC;
+import static org.h2.util.ParserUtil.AUTHORIZATION;
+import static org.h2.util.ParserUtil.BETWEEN;
+import static org.h2.util.ParserUtil.CASE;
+import static org.h2.util.ParserUtil.CAST;
+import static org.h2.util.ParserUtil.CHECK;
+import static org.h2.util.ParserUtil.CONSTRAINT;
+import static org.h2.util.ParserUtil.CROSS;
+import static org.h2.util.ParserUtil.CURRENT_CATALOG;
+import static org.h2.util.ParserUtil.CURRENT_DATE;
+import static org.h2.util.ParserUtil.CURRENT_PATH;
+import static org.h2.util.ParserUtil.CURRENT_ROLE;
+import static org.h2.util.ParserUtil.CURRENT_SCHEMA;
+import static org.h2.util.ParserUtil.CURRENT_TIME;
+import static org.h2.util.ParserUtil.CURRENT_TIMESTAMP;
+import static org.h2.util.ParserUtil.CURRENT_USER;
+import static org.h2.util.ParserUtil.DAY;
+import static org.h2.util.ParserUtil.DEFAULT;
+import static org.h2.util.ParserUtil.DISTINCT;
+import static org.h2.util.ParserUtil.ELSE;
+import static org.h2.util.ParserUtil.END;
+import static org.h2.util.ParserUtil.EXCEPT;
+import static org.h2.util.ParserUtil.EXISTS;
+import static org.h2.util.ParserUtil.FALSE;
+import static org.h2.util.ParserUtil.FETCH;
+import static org.h2.util.ParserUtil.FOR;
+import static org.h2.util.ParserUtil.FOREIGN;
+import static org.h2.util.ParserUtil.FROM;
+import static org.h2.util.ParserUtil.FULL;
+import static org.h2.util.ParserUtil.GROUP;
+import static org.h2.util.ParserUtil.HAVING;
+import static org.h2.util.ParserUtil.HOUR;
import static org.h2.util.ParserUtil.IDENTIFIER;
+import static org.h2.util.ParserUtil.IF;
+import static org.h2.util.ParserUtil.IN;
+import static org.h2.util.ParserUtil.INNER;
+import static org.h2.util.ParserUtil.INTERSECT;
+import static org.h2.util.ParserUtil.INTERVAL;
+import static org.h2.util.ParserUtil.IS;
+import static org.h2.util.ParserUtil.JOIN;
+import static org.h2.util.ParserUtil.KEY;
+import static org.h2.util.ParserUtil.LEFT;
+import static org.h2.util.ParserUtil.LIKE;
import static org.h2.util.ParserUtil.LIMIT;
+import static org.h2.util.ParserUtil.LOCALTIME;
+import static org.h2.util.ParserUtil.LOCALTIMESTAMP;
import static org.h2.util.ParserUtil.MINUS;
+import static org.h2.util.ParserUtil.MINUTE;
+import static org.h2.util.ParserUtil.MONTH;
+import static org.h2.util.ParserUtil.NATURAL;
+import static org.h2.util.ParserUtil.NOT;
+import static org.h2.util.ParserUtil.NULL;
+import static org.h2.util.ParserUtil.OFFSET;
+import static org.h2.util.ParserUtil.ON;
+import static org.h2.util.ParserUtil.OR;
+import static org.h2.util.ParserUtil.ORDER;
+import static org.h2.util.ParserUtil.PRIMARY;
+import static org.h2.util.ParserUtil.QUALIFY;
+import static org.h2.util.ParserUtil.RIGHT;
+import static org.h2.util.ParserUtil.ROW;
+import static org.h2.util.ParserUtil.ROWNUM;
+import static org.h2.util.ParserUtil.SECOND;
+import static org.h2.util.ParserUtil.SELECT;
+import static org.h2.util.ParserUtil.SESSION_USER;
+import static org.h2.util.ParserUtil.SET;
+import static org.h2.util.ParserUtil.SOME;
+import static org.h2.util.ParserUtil.SYMMETRIC;
+import static org.h2.util.ParserUtil.SYSTEM_USER;
+import static org.h2.util.ParserUtil.TABLE;
+import static org.h2.util.ParserUtil.TO;
+import static org.h2.util.ParserUtil.TRUE;
import static org.h2.util.ParserUtil.UESCAPE;
+import static org.h2.util.ParserUtil.UNION;
+import static org.h2.util.ParserUtil.UNIQUE;
+import static org.h2.util.ParserUtil.UNKNOWN;
+import static org.h2.util.ParserUtil.USER;
+import static org.h2.util.ParserUtil.USING;
+import static org.h2.util.ParserUtil.VALUE;
+import static org.h2.util.ParserUtil.VALUES;
+import static org.h2.util.ParserUtil.WHEN;
+import static org.h2.util.ParserUtil.WHERE;
+import static org.h2.util.ParserUtil.WINDOW;
+import static org.h2.util.ParserUtil.WITH;
+import static org.h2.util.ParserUtil.YEAR;
+import static org.h2.util.ParserUtil._ROWID_;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
@@ -48,7 +134,6 @@
import org.h2.api.ErrorCode;
import org.h2.engine.CastDataProvider;
import org.h2.message.DbException;
-import org.h2.util.ParserUtil;
import org.h2.util.StringUtils;
import org.h2.value.ValueBigint;
import org.h2.value.ValueDecfloat;
@@ -265,72 +350,108 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
token = new Token.KeywordToken(tokenStart, AT);
break;
case 'A':
- case 'B':
- case 'C':
- case 'D':
- case 'E':
- case 'F':
- case 'G':
- case 'H':
- case 'I':
- case 'J':
- case 'K':
- case 'L':
- case 'M':
- case 'O':
- case 'P':
- case 'Q':
- case 'R':
- case 'S':
- case 'T':
- case 'V':
- case 'W':
- case 'Y':
- case 'Z':
- case '_':
case 'a':
+ i = readA(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'B':
case 'b':
+ i = readB(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'C':
case 'c':
+ i = readC(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'D':
case 'd':
+ i = readD(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'E':
case 'e':
+ i = readE(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'F':
case 'f':
+ i = readF(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'G':
case 'g':
+ i = readG(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'H':
case 'h':
+ i = readH(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'I':
case 'i':
+ i = readI(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'J':
case 'j':
+ i = readJ(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'K':
case 'k':
+ i = readK(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'L':
case 'l':
+ i = readL(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'M':
case 'm':
- case 'o':
- case 'p':
- case 'q':
- case 'r':
- case 's':
- case 't':
- case 'v':
- case 'w':
- case 'y':
- case 'z':
- i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
- tokenStart, i, c, tokens);
+ i = readM(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
continue loop;
case 'N':
case 'n':
if (i < end && sql.charAt(i + 1) == '\'') {
i = readCharacterString(sql, provider, tokenStart, end, i + 1, false, tokens);
- continue loop;
+ } else {
+ i = readN(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
}
- i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
- tokenStart, i, c, tokens);
continue loop;
- case 'X':
- case 'x':
- if (i < end && sql.charAt(i + 1) == '\'') {
- i = readBinaryString(sql, provider, tokenStart, end, i + 1, tokens);
- continue loop;
- }
- i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
- tokenStart, i, c, tokens);
+ case 'O':
+ case 'o':
+ i = readO(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'P':
+ case 'p':
+ i = readP(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'Q':
+ case 'q':
+ i = readQ(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'R':
+ case 'r':
+ i = readR(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'S':
+ case 's':
+ i = readS(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'T':
+ case 't':
+ i = readT(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
continue loop;
case 'U':
case 'u':
@@ -347,8 +468,37 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
continue loop;
}
}
- i = readIdentifierOrKeyword(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords,
- tokenStart, i, c, tokens);
+ i = readU(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'V':
+ case 'v':
+ i = readV(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'W':
+ case 'w':
+ i = readW(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'X':
+ case 'x':
+ if (i < end && sql.charAt(i + 1) == '\'') {
+ i = readBinaryString(sql, provider, tokenStart, end, i + 1, tokens);
+ } else {
+ i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i, c,
+ tokens);
+ }
+ continue loop;
+ case 'Y':
+ case 'y':
+ i = readY(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
+ case 'Z':
+ case 'z':
+ i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i, c,
+ tokens);
continue loop;
case '[':
if (provider.getMode().squareBracketQuotedNames) {
@@ -365,6 +515,10 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
case ']':
token = new Token.KeywordToken(tokenStart, CLOSE_BRACKET);
break;
+ case '_':
+ i = read_(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
+ tokens);
+ continue loop;
case '{':
token = new Token.KeywordToken(tokenStart, OPEN_BRACE);
break;
@@ -409,33 +563,509 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
private static int readIdentifier(String sql, CastDataProvider provider, int end, boolean identifiersToUpper, //
boolean identifiersToLower, int tokenStart, int i, int cp, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i, cp);
+ if (cp >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+ i++;
+ }
+ int endIndex = findIdentifierEnd(sql, provider, end, i + Character.charCount(cp) - 1);
tokens.add(new Token.IdentifierToken(tokenStart,
extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), false, false));
return endIndex;
}
- private static int readIdentifierOrKeyword(String sql, CastDataProvider provider, int end,
- boolean identifiersToUpper, boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, char c,
- ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i, c);
- int type = ParserUtil.getTokenType(sql, true, tokenStart, endIndex - tokenStart, false);
- Token token;
- boolean keyword;
- switch (type) {
- case IDENTIFIER:
- keyword = false;
+ private static int readA(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (length == 2) {
+ type = (sql.charAt(tokenStart + 1) & 0xffdf) == 'S' ? AS : IDENTIFIER;
+ } else {
+ if (eq("ALL", sql, tokenStart, length)) {
+ type = ALL;
+ } else if (eq("AND", sql, tokenStart, length)) {
+ type = AND;
+ } else if (eq("ANY", sql, tokenStart, length)) {
+ type = ANY;
+ } else if (eq("ARRAY", sql, tokenStart, length)) {
+ type = ARRAY;
+ } else if (eq("ASYMMETRIC", sql, tokenStart, length)) {
+ type = ASYMMETRIC;
+ } else if (eq("AUTHORIZATION", sql, tokenStart, length)) {
+ type = AUTHORIZATION;
+ } else {
+ type = IDENTIFIER;
+ }
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readB(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("BETWEEN", sql, tokenStart, length) ? BETWEEN : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readC(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("CASE", sql, tokenStart, length)) {
+ type = CASE;
+ } else if (eq("CAST", sql, tokenStart, length)) {
+ type = CAST;
+ } else if (eq("CHECK", sql, tokenStart, length)) {
+ type = CHECK;
+ } else if (eq("CONSTRAINT", sql, tokenStart, length)) {
+ type = CONSTRAINT;
+ } else if (eq("CROSS", sql, tokenStart, length)) {
+ type = CROSS;
+ } else if (length >= 12 && eq("CURRENT_", sql, tokenStart, 8)) {
+ type = getTokenTypeCurrent(sql, tokenStart, length);
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int getTokenTypeCurrent(String s, int tokenStart, int length) {
+ tokenStart += 8;
+ switch (length) {
+ case 12:
+ if (eqCurrent("CURRENT_DATE", s, tokenStart, length)) {
+ return CURRENT_DATE;
+ } else if (eqCurrent("CURRENT_PATH", s, tokenStart, length)) {
+ return CURRENT_PATH;
+ } else if (eqCurrent("CURRENT_ROLE", s, tokenStart, length)) {
+ return CURRENT_ROLE;
+ } else if (eqCurrent("CURRENT_TIME", s, tokenStart, length)) {
+ return CURRENT_TIME;
+ } else if (eqCurrent("CURRENT_USER", s, tokenStart, length)) {
+ return CURRENT_USER;
+ }
break;
- case LIMIT:
- keyword = provider.getMode().limit;
+ case 14:
+ if (eqCurrent("CURRENT_SCHEMA", s, tokenStart, length)) {
+ return CURRENT_SCHEMA;
+ }
break;
- case MINUS:
- keyword = provider.getMode().minusIsExcept;
+ case 15:
+ if (eqCurrent("CURRENT_CATALOG", s, tokenStart, length)) {
+ return CURRENT_CATALOG;
+ }
break;
- default:
- keyword = true;
+ case 17:
+ if (eqCurrent("CURRENT_TIMESTAMP", s, tokenStart, length)) {
+ return CURRENT_TIMESTAMP;
+ }
+ }
+ return IDENTIFIER;
+ }
+
+ private static boolean eqCurrent(String expected, String s, int start, int length) {
+ for (int i = 8; i < length; i++) {
+ if (expected.charAt(i) != (s.charAt(start++) & 0xffdf)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int readD(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("DAY", sql, tokenStart, length)) {
+ type = DAY;
+ } else if (eq("DEFAULT", sql, tokenStart, length)) {
+ type = DEFAULT;
+ } else if (eq("DISTINCT", sql, tokenStart, length)) {
+ type = DISTINCT;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readE(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("ELSE", sql, tokenStart, length)) {
+ type = ELSE;
+ } else if (eq("END", sql, tokenStart, length)) {
+ type = END;
+ } else if (eq("EXCEPT", sql, tokenStart, length)) {
+ type = EXCEPT;
+ } else if (eq("EXISTS", sql, tokenStart, length)) {
+ type = EXISTS;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readF(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("FETCH", sql, tokenStart, length)) {
+ type = FETCH;
+ } else if (eq("FROM", sql, tokenStart, length)) {
+ type = FROM;
+ } else if (eq("FOR", sql, tokenStart, length)) {
+ type = FOR;
+ } else if (eq("FOREIGN", sql, tokenStart, length)) {
+ type = FOREIGN;
+ } else if (eq("FULL", sql, tokenStart, length)) {
+ type = FULL;
+ } else if (eq("FALSE", sql, tokenStart, length)) {
+ type = FALSE;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readG(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("GROUP", sql, tokenStart, length) ? GROUP : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readH(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("HAVING", sql, tokenStart, length)) {
+ type = HAVING;
+ } else if (eq("HOUR", sql, tokenStart, length)) {
+ type = HOUR;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readI(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (length == 2) {
+ switch ((sql.charAt(tokenStart + 1) & 0xffdf)) {
+ case 'F':
+ type = IF;
+ break;
+ case 'N':
+ type = IN;
+ break;
+ case 'S':
+ type = IS;
+ break;
+ default:
+ type = IDENTIFIER;
+ }
+ } else {
+ if (eq("INNER", sql, tokenStart, length)) {
+ type = INNER;
+ } else if (eq("INTERSECT", sql, tokenStart, length)) {
+ type = INTERSECT;
+ } else if (eq("INTERVAL", sql, tokenStart, length)) {
+ type = INTERVAL;
+ } else {
+ type = IDENTIFIER;
+ }
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readJ(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("JOIN", sql, tokenStart, length) ? JOIN : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readK(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("KEY", sql, tokenStart, length) ? KEY : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readL(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("LEFT", sql, tokenStart, length)) {
+ type = LEFT;
+ } else if (eq("LIMIT", sql, tokenStart, length)) {
+ type = provider.getMode().limit ? LIMIT : IDENTIFIER;
+ } else if (eq("LIKE", sql, tokenStart, length)) {
+ type = LIKE;
+ } else if (eq("LOCALTIME", sql, tokenStart, length)) {
+ type = LOCALTIME;
+ } else if (eq("LOCALTIMESTAMP", sql, tokenStart, length)) {
+ type = LOCALTIMESTAMP;
+ } else {
+ type = IDENTIFIER;
}
- if (!keyword) {
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readM(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("MINUS", sql, tokenStart, length)) {
+ type = provider.getMode().minusIsExcept ? MINUS : IDENTIFIER;
+ } else if (eq("MINUTE", sql, tokenStart, length)) {
+ type = MINUTE;
+ } else if (eq("MONTH", sql, tokenStart, length)) {
+ type = MONTH;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readN(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("NOT", sql, tokenStart, length)) {
+ type = NOT;
+ } else if (eq("NATURAL", sql, tokenStart, length)) {
+ type = NATURAL;
+ } else if (eq("NULL", sql, tokenStart, length)) {
+ type = NULL;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readO(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (length == 2) {
+ switch ((sql.charAt(tokenStart + 1) & 0xffdf)) {
+ case 'N':
+ type = ON;
+ break;
+ case 'R':
+ type = OR;
+ break;
+ default:
+ type = IDENTIFIER;
+ }
+ } else {
+ if (eq("OFFSET", sql, tokenStart, length)) {
+ type = OFFSET;
+ } else if (eq("ORDER", sql, tokenStart, length)) {
+ type = ORDER;
+ } else {
+ type = IDENTIFIER;
+ }
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readP(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("PRIMARY", sql, tokenStart, length) ? PRIMARY : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readQ(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("QUALIFY", sql, tokenStart, length) ? QUALIFY : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readR(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("RIGHT", sql, tokenStart, length)) {
+ type = RIGHT;
+ } else if (eq("ROW", sql, tokenStart, length)) {
+ type = ROW;
+ } else if (eq("ROWNUM", sql, tokenStart, length)) {
+ type = ROWNUM;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readS(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("SECOND", sql, tokenStart, length)) {
+ type = SECOND;
+ } else if (eq("SELECT", sql, tokenStart, length)) {
+ type = SELECT;
+ } else if (eq("SESSION_USER", sql, tokenStart, length)) {
+ type = SESSION_USER;
+ } else if (eq("SET", sql, tokenStart, length)) {
+ type = SET;
+ } else if (eq("SOME", sql, tokenStart, length)) {
+ type = SOME;
+ } else if (eq("SYMMETRIC", sql, tokenStart, length)) {
+ type = SYMMETRIC;
+ } else if (eq("SYSTEM_USER", sql, tokenStart, length)) {
+ type = SYSTEM_USER;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readT(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (length == 2) {
+ type = (sql.charAt(tokenStart + 1) & 0xffdf) == 'O' ? TO : IDENTIFIER;
+ } else {
+ if (eq("TABLE", sql, tokenStart, length)) {
+ type = TABLE;
+ } else if (eq("TRUE", sql, tokenStart, length)) {
+ type = TRUE;
+ } else {
+ type = IDENTIFIER;
+ }
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readU(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("UESCAPE", sql, tokenStart, length)) {
+ type = UESCAPE;
+ } else if (eq("UNION", sql, tokenStart, length)) {
+ type = UNION;
+ } else if (eq("UNIQUE", sql, tokenStart, length)) {
+ type = UNIQUE;
+ } else if (eq("UNKNOWN", sql, tokenStart, length)) {
+ type = UNKNOWN;
+ } else if (eq("USER", sql, tokenStart, length)) {
+ type = USER;
+ } else if (eq("USING", sql, tokenStart, length)) {
+ type = USING;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readV(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("VALUE", sql, tokenStart, length)) {
+ type = VALUE;
+ } else if (eq("VALUES", sql, tokenStart, length)) {
+ type = VALUES;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readW(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type;
+ if (eq("WHEN", sql, tokenStart, length)) {
+ type = WHEN;
+ } else if (eq("WHERE", sql, tokenStart, length)) {
+ type = WHERE;
+ } else if (eq("WINDOW", sql, tokenStart, length)) {
+ type = WINDOW;
+ } else if (eq("WITH", sql, tokenStart, length)) {
+ type = WITH;
+ } else {
+ type = IDENTIFIER;
+ }
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readY(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int length = endIndex - tokenStart;
+ int type = eq("YEAR", sql, tokenStart, length) ? YEAR : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int read_(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
+ boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, provider, end, i);
+ int type = endIndex - tokenStart == 7 && "_ROWID_".regionMatches(true, 1, sql, tokenStart + 1, 6) ? _ROWID_
+ : IDENTIFIER;
+ return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
+ endIndex, type);
+ }
+
+ private static int readIdentifierOrKeyword(String sql, boolean identifiersToUpper, boolean identifiersToLower,
+ BitSet nonKeywords, int tokenStart, ArrayList tokens, int endIndex, int type) {
+ Token token;
+ if (type == IDENTIFIER) {
token = new Token.IdentifierToken(tokenStart,
extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), //
false, false);
@@ -449,16 +1079,29 @@ private static int readIdentifierOrKeyword(String sql, CastDataProvider provider
return endIndex;
}
- private static int findIdentifierEnd(String sql, CastDataProvider provider, int end, int i, int cp) {
- int next = i;
- for (;; i = next) {
- next += Character.charCount(cp);
- if (next > end || (!Character.isJavaIdentifierPart(cp = sql.codePointAt(next))
+ private static boolean eq(String expected, String s, int start, int length) {
+ if (length != expected.length()) {
+ return false;
+ }
+ for (int i = 1; i < length; i++) {
+ if (expected.charAt(i) != (s.charAt(++start) & 0xffdf)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int findIdentifierEnd(String sql, CastDataProvider provider, int end, int i) {
+ i++;
+ for (;;) {
+ int cp;
+ if (i > end || (!Character.isJavaIdentifierPart(cp = sql.codePointAt(i))
&& (cp != '#' || !provider.getMode().supportPoundSymbolForColumnNames))) {
break;
}
+ i += Character.charCount(cp);
}
- return next;
+ return i;
}
private static String extractIdentifier(String sql, boolean identifiersToUpper, boolean identifiersToLower,
From 0e29c75403697faa93cf69704274739b068bffd3 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Tue, 4 Jan 2022 19:22:38 +0800
Subject: [PATCH 36/73] Remove complex code from ParserUtil
---
h2/src/main/org/h2/command/Parser.java | 2 +-
h2/src/main/org/h2/util/ParserUtil.java | 481 +++++-------------
.../test/org/h2/test/unit/TestKeywords.java | 2 +-
h2/src/tools/org/h2/build/doc/dictionary.txt | 2 +-
4 files changed, 123 insertions(+), 364 deletions(-)
diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java
index 3cfd724354..005b36e3d5 100644
--- a/h2/src/main/org/h2/command/Parser.java
+++ b/h2/src/main/org/h2/command/Parser.java
@@ -7304,7 +7304,7 @@ private String readStringOrIdentifier() {
}
private boolean isReservedFunctionName(String name) {
- int tokenType = ParserUtil.getTokenType(name, false, 0, name.length(), false);
+ int tokenType = ParserUtil.getTokenType(name, false, false);
if (tokenType != ParserUtil.IDENTIFIER) {
if (database.isAllowBuiltinAliasOverride()) {
switch (tokenType) {
diff --git a/h2/src/main/org/h2/util/ParserUtil.java b/h2/src/main/org/h2/util/ParserUtil.java
index 3e0fabc9c9..95498a4a26 100644
--- a/h2/src/main/org/h2/util/ParserUtil.java
+++ b/h2/src/main/org/h2/util/ParserUtil.java
@@ -5,6 +5,8 @@
*/
package org.h2.util;
+import java.util.HashMap;
+
public class ParserUtil {
/**
@@ -476,6 +478,115 @@ public class ParserUtil {
*/
public static final int LAST_KEYWORD = _ROWID_;
+ private static final HashMap KEYWORDS;
+
+ static {
+ HashMap map = new HashMap<>(256);
+ map.put("ALL", ALL);
+ map.put("AND", AND);
+ map.put("ANY", ANY);
+ map.put("ARRAY", ARRAY);
+ map.put("AS", AS);
+ map.put("ASYMMETRIC", ASYMMETRIC);
+ map.put("AUTHORIZATION", AUTHORIZATION);
+ map.put("BETWEEN", BETWEEN);
+ map.put("CASE", CASE);
+ map.put("CAST", CAST);
+ map.put("CHECK", CHECK);
+ map.put("CONSTRAINT", CONSTRAINT);
+ map.put("CROSS", CROSS);
+ map.put("CURRENT_CATALOG", CURRENT_CATALOG);
+ map.put("CURRENT_DATE", CURRENT_DATE);
+ map.put("CURRENT_PATH", CURRENT_PATH);
+ map.put("CURRENT_ROLE", CURRENT_ROLE);
+ map.put("CURRENT_SCHEMA", CURRENT_SCHEMA);
+ map.put("CURRENT_TIME", CURRENT_TIME);
+ map.put("CURRENT_TIMESTAMP", CURRENT_TIMESTAMP);
+ map.put("CURRENT_USER", CURRENT_USER);
+ map.put("DAY", DAY);
+ map.put("DEFAULT", DEFAULT);
+ map.put("DISTINCT", DISTINCT);
+ map.put("ELSE", ELSE);
+ map.put("END", END);
+ map.put("EXCEPT", EXCEPT);
+ map.put("EXISTS", EXISTS);
+ map.put("FALSE", FALSE);
+ map.put("FETCH", FETCH);
+ map.put("FOR", FOR);
+ map.put("FOREIGN", FOREIGN);
+ map.put("FROM", FROM);
+ map.put("FULL", FULL);
+ map.put("GROUP", GROUP);
+ map.put("HAVING", HAVING);
+ map.put("HOUR", HOUR);
+ map.put("IF", IF);
+ map.put("IN", IN);
+ map.put("INNER", INNER);
+ map.put("INTERSECT", INTERSECT);
+ map.put("INTERVAL", INTERVAL);
+ map.put("IS", IS);
+ map.put("JOIN", JOIN);
+ map.put("KEY", KEY);
+ map.put("LEFT", LEFT);
+ map.put("LIKE", LIKE);
+ map.put("LIMIT", LIMIT);
+ map.put("LOCALTIME", LOCALTIME);
+ map.put("LOCALTIMESTAMP", LOCALTIMESTAMP);
+ map.put("MINUS", MINUS);
+ map.put("MINUTE", MINUTE);
+ map.put("MONTH", MONTH);
+ map.put("NATURAL", NATURAL);
+ map.put("NOT", NOT);
+ map.put("NULL", NULL);
+ map.put("OFFSET", OFFSET);
+ map.put("ON", ON);
+ map.put("OR", OR);
+ map.put("ORDER", ORDER);
+ map.put("PRIMARY", PRIMARY);
+ map.put("QUALIFY", QUALIFY);
+ map.put("RIGHT", RIGHT);
+ map.put("ROW", ROW);
+ map.put("ROWNUM", ROWNUM);
+ map.put("SECOND", SECOND);
+ map.put("SELECT", SELECT);
+ map.put("SESSION_USER", SESSION_USER);
+ map.put("SET", SET);
+ map.put("SOME", SOME);
+ map.put("SYMMETRIC", SYMMETRIC);
+ map.put("SYSTEM_USER", SYSTEM_USER);
+ map.put("TABLE", TABLE);
+ map.put("TO", TO);
+ map.put("TRUE", TRUE);
+ map.put("UESCAPE", UESCAPE);
+ map.put("UNION", UNION);
+ map.put("UNIQUE", UNIQUE);
+ map.put("UNKNOWN", UNKNOWN);
+ map.put("USER", USER);
+ map.put("USING", USING);
+ map.put("VALUE", VALUE);
+ map.put("VALUES", VALUES);
+ map.put("WHEN", WHEN);
+ map.put("WHERE", WHERE);
+ map.put("WINDOW", WINDOW);
+ map.put("WITH", WITH);
+ map.put("YEAR", YEAR);
+ map.put("_ROWID_", _ROWID_);
+ // Additional keywords
+ map.put("BOTH", KEYWORD);
+ map.put("FILTER", KEYWORD);
+ map.put("GROUPS", KEYWORD);
+ map.put("ILIKE", KEYWORD);
+ map.put("LEADING", KEYWORD);
+ map.put("OVER", KEYWORD);
+ map.put("PARTITION", KEYWORD);
+ map.put("RANGE", KEYWORD);
+ map.put("REGEXP", KEYWORD);
+ map.put("ROWS", KEYWORD);
+ map.put("TOP", KEYWORD);
+ map.put("TRAILING", KEYWORD);
+ KEYWORDS = map;
+ }
+
private ParserUtil() {
// utility class
}
@@ -508,7 +619,7 @@ public static StringBuilder quoteIdentifier(StringBuilder builder, String s, int
* @return true if it is a keyword
*/
public static boolean isKeyword(String s, boolean ignoreCase) {
- return getTokenType(s, ignoreCase, 0, s.length(), false) != IDENTIFIER;
+ return getTokenType(s, ignoreCase, false) != IDENTIFIER;
}
/**
@@ -534,7 +645,7 @@ public static boolean isSimpleIdentifier(String s, boolean databaseToUpper, bool
return false;
}
}
- return getTokenType(s, !databaseToUpper, 0, length, true) == IDENTIFIER;
+ return getTokenType(s, !databaseToUpper, true) == IDENTIFIER;
}
private static boolean checkLetter(boolean databaseToUpper, boolean databaseToLower, char c) {
@@ -560,377 +671,25 @@ private static boolean checkLetter(boolean databaseToUpper, boolean databaseToLo
* @param s the string with token
* @param ignoreCase true if case should be ignored, false if only upper case
* tokens are detected as keywords
- * @param start start index of token
- * @param length length of token; must be positive
* @param additionalKeywords
* whether context-sensitive keywords are returned as
* {@link #KEYWORD}
* @return the token type
*/
- public static int getTokenType(String s, boolean ignoreCase, int start, int length, boolean additionalKeywords) {
+ public static int getTokenType(String s, boolean ignoreCase, boolean additionalKeywords) {
+ int length = s.length();
if (length <= 1 || length > 17) {
return IDENTIFIER;
}
- /*
- * DatabaseMetaLocal.getSQLKeywords() and tests should be updated when new
- * non-SQL:2003 keywords are introduced here.
- */
- char c1 = s.charAt(start);
if (ignoreCase) {
- // Convert a-z to A-Z and 0x7f to _ (need special handling).
- c1 &= 0xffdf;
- }
- if (length == 2) {
- char c2 = s.charAt(start + 1);
- if (ignoreCase) {
- c2 &= 0xffdf;
- }
- switch (c1) {
- case 'A':
- if (c2 == 'S') {
- return AS;
- }
- return IDENTIFIER;
- case 'I':
- if (c2 == 'F') {
- return IF;
- } else if (c2 == 'N') {
- return IN;
- } else if (c2 == 'S') {
- return IS;
- }
- return IDENTIFIER;
- case 'O':
- if (c2 == 'N') {
- return ON;
- } else if (c2 == 'R') {
- return OR;
- }
- return IDENTIFIER;
- case 'T':
- if (c2 == 'O') {
- return TO;
- }
- //$FALL-THROUGH$
- default:
- return IDENTIFIER;
- }
+ s = StringUtils.toUpperEnglish(s);
}
- switch (c1) {
- case 'A':
- if (eq("ALL", s, ignoreCase, start, length)) {
- return ALL;
- } else if (eq("AND", s, ignoreCase, start, length)) {
- return AND;
- } else if (eq("ANY", s, ignoreCase, start, length)) {
- return ANY;
- } else if (eq("ARRAY", s, ignoreCase, start, length)) {
- return ARRAY;
- } else if (eq("ASYMMETRIC", s, ignoreCase, start, length)) {
- return ASYMMETRIC;
- } else if (eq("AUTHORIZATION", s, ignoreCase, start, length)) {
- return AUTHORIZATION;
- }
- return IDENTIFIER;
- case 'B':
- if (eq("BETWEEN", s, ignoreCase, start, length)) {
- return BETWEEN;
- }
- if (additionalKeywords) {
- if (eq("BOTH", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'C':
- if (eq("CASE", s, ignoreCase, start, length)) {
- return CASE;
- } else if (eq("CAST", s, ignoreCase, start, length)) {
- return CAST;
- } else if (eq("CHECK", s, ignoreCase, start, length)) {
- return CHECK;
- } else if (eq("CONSTRAINT", s, ignoreCase, start, length)) {
- return CONSTRAINT;
- } else if (eq("CROSS", s, ignoreCase, start, length)) {
- return CROSS;
- } else if (length >= 12 && "CURRENT_".regionMatches(ignoreCase, 1, s, start + 1, 7)) {
- return getTokenTypeCurrent(s, ignoreCase, start, length);
- }
- return IDENTIFIER;
- case 'D':
- if (eq("DAY", s, ignoreCase, start, length)) {
- return DAY;
- } else if (eq("DEFAULT", s, ignoreCase, start, length)) {
- return DEFAULT;
- } else if (eq("DISTINCT", s, ignoreCase, start, length)) {
- return DISTINCT;
- }
- return IDENTIFIER;
- case 'E':
- if (eq("ELSE", s, ignoreCase, start, length)) {
- return ELSE;
- } else if (eq("END", s, ignoreCase, start, length)) {
- return END;
- } else if (eq("EXCEPT", s, ignoreCase, start, length)) {
- return EXCEPT;
- } else if (eq("EXISTS", s, ignoreCase, start, length)) {
- return EXISTS;
- }
- return IDENTIFIER;
- case 'F':
- if (eq("FETCH", s, ignoreCase, start, length)) {
- return FETCH;
- } else if (eq("FROM", s, ignoreCase, start, length)) {
- return FROM;
- } else if (eq("FOR", s, ignoreCase, start, length)) {
- return FOR;
- } else if (eq("FOREIGN", s, ignoreCase, start, length)) {
- return FOREIGN;
- } else if (eq("FULL", s, ignoreCase, start, length)) {
- return FULL;
- } else if (eq("FALSE", s, ignoreCase, start, length)) {
- return FALSE;
- }
- if (additionalKeywords) {
- if (eq("FILTER", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'G':
- if (eq("GROUP", s, ignoreCase, start, length)) {
- return GROUP;
- }
- if (additionalKeywords) {
- if (eq("GROUPS", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'H':
- if (eq("HAVING", s, ignoreCase, start, length)) {
- return HAVING;
- } else if (eq("HOUR", s, ignoreCase, start, length)) {
- return HOUR;
- }
- return IDENTIFIER;
- case 'I':
- if (eq("INNER", s, ignoreCase, start, length)) {
- return INNER;
- } else if (eq("INTERSECT", s, ignoreCase, start, length)) {
- return INTERSECT;
- } else if (eq("INTERVAL", s, ignoreCase, start, length)) {
- return INTERVAL;
- }
- if (additionalKeywords) {
- if (eq("ILIKE", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'J':
- if (eq("JOIN", s, ignoreCase, start, length)) {
- return JOIN;
- }
- return IDENTIFIER;
- case 'K':
- if (eq("KEY", s, ignoreCase, start, length)) {
- return KEY;
- }
- return IDENTIFIER;
- case 'L':
- if (eq("LEFT", s, ignoreCase, start, length)) {
- return LEFT;
- } else if (eq("LIMIT", s, ignoreCase, start, length)) {
- return LIMIT;
- } else if (eq("LIKE", s, ignoreCase, start, length)) {
- return LIKE;
- } else if (eq("LOCALTIME", s, ignoreCase, start, length)) {
- return LOCALTIME;
- } else if (eq("LOCALTIMESTAMP", s, ignoreCase, start, length)) {
- return LOCALTIMESTAMP;
- }
- if (additionalKeywords) {
- if (eq("LEADING", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'M':
- if (eq("MINUS", s, ignoreCase, start, length)) {
- return MINUS;
- } else if (eq("MINUTE", s, ignoreCase, start, length)) {
- return MINUTE;
- } else if (eq("MONTH", s, ignoreCase, start, length)) {
- return MONTH;
- }
+ Integer type = KEYWORDS.get(s);
+ if (type == null) {
return IDENTIFIER;
- case 'N':
- if (eq("NOT", s, ignoreCase, start, length)) {
- return NOT;
- } else if (eq("NATURAL", s, ignoreCase, start, length)) {
- return NATURAL;
- } else if (eq("NULL", s, ignoreCase, start, length)) {
- return NULL;
- }
- return IDENTIFIER;
- case 'O':
- if (eq("OFFSET", s, ignoreCase, start, length)) {
- return OFFSET;
- } else if (eq("ORDER", s, ignoreCase, start, length)) {
- return ORDER;
- }
- if (additionalKeywords) {
- if (eq("OVER", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'P':
- if (eq("PRIMARY", s, ignoreCase, start, length)) {
- return PRIMARY;
- }
- if (additionalKeywords) {
- if (eq("PARTITION", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'Q':
- if (eq("QUALIFY", s, ignoreCase, start, length)) {
- return QUALIFY;
- }
- return IDENTIFIER;
- case 'R':
- if (eq("RIGHT", s, ignoreCase, start, length)) {
- return RIGHT;
- } else if (eq("ROW", s, ignoreCase, start, length)) {
- return ROW;
- } else if (eq("ROWNUM", s, ignoreCase, start, length)) {
- return ROWNUM;
- }
- if (additionalKeywords) {
- if (eq("RANGE", s, ignoreCase, start, length) || eq("REGEXP", s, ignoreCase, start, length)
- || eq("ROWS", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'S':
- if (eq("SECOND", s, ignoreCase, start, length)) {
- return SECOND;
- } else if (eq("SELECT", s, ignoreCase, start, length)) {
- return SELECT;
- } else if (eq("SESSION_USER", s, ignoreCase, start, length)) {
- return SESSION_USER;
- } else if (eq("SET", s, ignoreCase, start, length)) {
- return SET;
- } else if (eq("SOME", s, ignoreCase, start, length)) {
- return SOME;
- } else if (eq("SYMMETRIC", s, ignoreCase, start, length)) {
- return SYMMETRIC;
- } else if (eq("SYSTEM_USER", s, ignoreCase, start, length)) {
- return SYSTEM_USER;
- }
- return IDENTIFIER;
- case 'T':
- if (eq("TABLE", s, ignoreCase, start, length)) {
- return TABLE;
- } else if (eq("TRUE", s, ignoreCase, start, length)) {
- return TRUE;
- }
- if (additionalKeywords) {
- if (eq("TOP", s, ignoreCase, start, length) || eq("TRAILING", s, ignoreCase, start, length)) {
- return KEYWORD;
- }
- }
- return IDENTIFIER;
- case 'U':
- if (eq("UESCAPE", s, ignoreCase, start, length)) {
- return UESCAPE;
- } else if (eq("UNION", s, ignoreCase, start, length)) {
- return UNION;
- } else if (eq("UNIQUE", s, ignoreCase, start, length)) {
- return UNIQUE;
- } else if (eq("UNKNOWN", s, ignoreCase, start, length)) {
- return UNKNOWN;
- } else if (eq("USER", s, ignoreCase, start, length)) {
- return USER;
- } else if (eq("USING", s, ignoreCase, start, length)) {
- return USING;
- }
- return IDENTIFIER;
- case 'V':
- if (eq("VALUE", s, ignoreCase, start, length)) {
- return VALUE;
- } else if (eq("VALUES", s, ignoreCase, start, length)) {
- return VALUES;
- }
- return IDENTIFIER;
- case 'W':
- if (eq("WHEN", s, ignoreCase, start, length)) {
- return WHEN;
- } else if (eq("WHERE", s, ignoreCase, start, length)) {
- return WHERE;
- } else if (eq("WINDOW", s, ignoreCase, start, length)) {
- return WINDOW;
- } else if (eq("WITH", s, ignoreCase, start, length)) {
- return WITH;
- }
- return IDENTIFIER;
- case 'Y':
- if (eq("YEAR", s, ignoreCase, start, length)) {
- return YEAR;
- }
- return IDENTIFIER;
- case '_':
- // Cannot use eq() because 0x7f can be converted to '_' (0x5f)
- if (length == 7 && "_ROWID_".regionMatches(ignoreCase, 0, s, start, 7)) {
- return _ROWID_;
- }
- //$FALL-THROUGH$
- default:
- return IDENTIFIER;
- }
- }
-
- private static boolean eq(String expected, String s, boolean ignoreCase, int start, int length) {
- // First letter was already checked
- return length == expected.length() && expected.regionMatches(ignoreCase, 1, s, start + 1, length - 1);
- }
-
- private static int getTokenTypeCurrent(String s, boolean ignoreCase, int start, int length) {
- start += 8;
- switch (length -= 8) {
- case 4:
- if ("CURRENT_DATE".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_DATE;
- } else if ("CURRENT_PATH".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_PATH;
- } else if ("CURRENT_ROLE".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_ROLE;
- } else if ("CURRENT_TIME".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_TIME;
- } else if ("CURRENT_USER".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_USER;
- }
- break;
- case 6:
- if ("CURRENT_SCHEMA".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_SCHEMA;
- }
- break;
- case 7:
- if ("CURRENT_CATALOG".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_CATALOG;
- }
- break;
- case 9:
- if ("CURRENT_TIMESTAMP".regionMatches(ignoreCase, 8, s, start, length)) {
- return CURRENT_TIMESTAMP;
- }
}
- return IDENTIFIER;
+ int t = type;
+ return t == KEYWORD && !additionalKeywords ? IDENTIFIER : t;
}
}
diff --git a/h2/src/test/org/h2/test/unit/TestKeywords.java b/h2/src/test/org/h2/test/unit/TestKeywords.java
index 65430b6028..b006b5dcb9 100644
--- a/h2/src/test/org/h2/test/unit/TestKeywords.java
+++ b/h2/src/test/org/h2/test/unit/TestKeywords.java
@@ -524,7 +524,7 @@ void add(Object value) {
}
}
final TokenType type;
- switch (ParserUtil.getTokenType(s, false, 0, l, true)) {
+ switch (ParserUtil.getTokenType(s, false, true)) {
case ParserUtil.IDENTIFIER:
type = TokenType.IDENTIFIER;
break;
diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt
index 440a895008..9eefefa01b 100644
--- a/h2/src/tools/org/h2/build/doc/dictionary.txt
+++ b/h2/src/tools/org/h2/build/doc/dictionary.txt
@@ -847,4 +847,4 @@ ptf overlay precedes regr slope sqlerror multiset submultiset inout sxx sxy inte
orientation eternal consideration erased fedc npgsql powers fffd uencode ampersand noversion ude considerable intro
entirely skeleton discouraged pearson coefficient squares covariance mytab debuggers fonts glyphs
filestore backstop tie breaker lockable lobtx btx waiter accounted aiobe spf resolvers generators
-accidental wbr subtree recognising
+accidental wbr subtree recognising supplementary
From f9fbe63f77b180d5720d42e310b9a2444dbe37a8 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Tue, 4 Jan 2022 21:39:49 +0800
Subject: [PATCH 37/73] Get data types directly from linked tables from H2
---
h2/src/main/org/h2/jdbc/JdbcResultSet.java | 9 +++++++++
h2/src/main/org/h2/table/TableLink.java | 16 ++++++++++++++--
h2/src/test/org/h2/test/db/TestLinkedTable.java | 7 +++++--
3 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/h2/src/main/org/h2/jdbc/JdbcResultSet.java b/h2/src/main/org/h2/jdbc/JdbcResultSet.java
index 78609ea4c8..2168c2dd2f 100644
--- a/h2/src/main/org/h2/jdbc/JdbcResultSet.java
+++ b/h2/src/main/org/h2/jdbc/JdbcResultSet.java
@@ -4277,4 +4277,13 @@ public Value[] getUpdateRow() {
return updateRow;
}
+ /**
+ * INTERNAL
+ *
+ * @return result
+ */
+ public ResultInterface getResult() {
+ return result;
+ }
+
}
diff --git a/h2/src/main/org/h2/table/TableLink.java b/h2/src/main/org/h2/table/TableLink.java
index 5d297a4a06..ca34042e66 100644
--- a/h2/src/main/org/h2/table/TableLink.java
+++ b/h2/src/main/org/h2/table/TableLink.java
@@ -24,8 +24,10 @@
import org.h2.index.IndexType;
import org.h2.index.LinkedIndex;
import org.h2.jdbc.JdbcConnection;
+import org.h2.jdbc.JdbcResultSet;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
+import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.schema.Schema;
import org.h2.util.JdbcUtils;
@@ -177,10 +179,20 @@ private void readMetaData() throws SQLException {
try (Statement stat = conn.getConnection().createStatement();
ResultSet rs = stat.executeQuery("SELECT * FROM " + qualifiedTableName + " T WHERE 1=0")) {
- if (columnList.isEmpty()) {
+ if (rs instanceof JdbcResultSet) {
+ ResultInterface result = ((JdbcResultSet) rs).getResult();
+ columnList.clear();
+ columnMap.clear();
+ for (int i = 0, l = result.getVisibleColumnCount(); i < l;) {
+ String n = result.getColumnName(i);
+ Column col = new Column(n, result.getColumnType(i), this, ++i);
+ columnList.add(col);
+ columnMap.put(n, col);
+ }
+ } else if (columnList.isEmpty()) {
// alternative solution
ResultSetMetaData rsMeta = rs.getMetaData();
- for (int i = 0; i < rsMeta.getColumnCount();) {
+ for (int i = 0, l = rsMeta.getColumnCount(); i < l;) {
String n = rsMeta.getColumnName(i + 1);
n = convertColumnName(n);
int sqlType = rsMeta.getColumnType(i + 1);
diff --git a/h2/src/test/org/h2/test/db/TestLinkedTable.java b/h2/src/test/org/h2/test/db/TestLinkedTable.java
index 93b876925c..d33f137c67 100644
--- a/h2/src/test/org/h2/test/db/TestLinkedTable.java
+++ b/h2/src/test/org/h2/test/db/TestLinkedTable.java
@@ -703,14 +703,17 @@ private void testGeometry() throws SQLException {
Connection cb = DriverManager.getConnection("jdbc:h2:mem:two", "sa", "sa");
Statement sa = ca.createStatement();
Statement sb = cb.createStatement();
- sa.execute("CREATE TABLE TEST(ID SERIAL, the_geom geometry)");
- sa.execute("INSERT INTO TEST(THE_GEOM) VALUES('POINT (1 1)')");
+ sa.execute("CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,"
+ + " THE_GEOM GEOMETRY, THE_GEOM_2 GEOMETRY(POINT, 4326))");
+ sa.execute("INSERT INTO TEST(THE_GEOM, THE_GEOM_2) VALUES"
+ + " (GEOMETRY 'POINT (1 1)', GEOMETRY 'SRID=4326;POINT(2 2)')");
String sql = "CREATE LINKED TABLE T(NULL, " +
"'jdbc:h2:mem:one', 'sa', 'sa', 'TEST') READONLY";
sb.execute(sql);
try (ResultSet rs = sb.executeQuery("SELECT * FROM T")) {
assertTrue(rs.next());
assertEquals("POINT (1 1)", rs.getString("THE_GEOM"));
+ assertEquals("SRID=4326;POINT (2 2)", rs.getString("THE_GEOM_2"));
}
sb.execute("DROP TABLE T");
ca.close();
From 956c6241868332c5b440f5d55ea8fdc1e51ae4fd Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Tue, 4 Jan 2022 21:56:32 +0800
Subject: [PATCH 38/73] Check URL scheme
---
h2/src/main/org/h2/util/JdbcUtils.java | 3 +++
h2/src/test/org/h2/test/unit/TestTools.java | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/h2/src/main/org/h2/util/JdbcUtils.java b/h2/src/main/org/h2/util/JdbcUtils.java
index d43a9c0f7b..f261a8438d 100644
--- a/h2/src/main/org/h2/util/JdbcUtils.java
+++ b/h2/src/main/org/h2/util/JdbcUtils.java
@@ -315,6 +315,9 @@ public static Connection getConnection(String driver, String url, String user, S
}
throw new SQLException("Driver " + driver + " is not suitable for " + url, "08001");
} else if (javax.naming.Context.class.isAssignableFrom(d)) {
+ if (!url.startsWith("java:")) {
+ throw new SQLException("Only java scheme is supported for JNDI lookups", "08001");
+ }
// JNDI context
Context context = (Context) d.getDeclaredConstructor().newInstance();
DataSource ds = (DataSource) context.lookup(url);
diff --git a/h2/src/test/org/h2/test/unit/TestTools.java b/h2/src/test/org/h2/test/unit/TestTools.java
index 1416092316..81c3f48d10 100644
--- a/h2/src/test/org/h2/test/unit/TestTools.java
+++ b/h2/src/test/org/h2/test/unit/TestTools.java
@@ -531,6 +531,13 @@ private void testJdbcDriverUtils() {
} catch (SQLException e) {
assertEquals("08001", e.getSQLState());
}
+ try {
+ JdbcUtils.getConnection("javax.naming.InitialContext", "ldap://localhost/ds", "sa", "");
+ fail("Expected SQLException: 08001");
+ } catch (SQLException e) {
+ assertEquals("08001", e.getSQLState());
+ assertEquals("Only java scheme is supported for JNDI lookups", e.getMessage());
+ }
}
private void testWrongServer() throws Exception {
From ded29308f91c7fb188fd122930dc98d4cc4787eb Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Wed, 5 Jan 2022 09:32:36 +0800
Subject: [PATCH 39/73] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3105990c31..9a10b96796 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ More information: https://h2database.com
com.h2database
h2
- 2.0.204
+ 2.0.206
```
From f3dd491c4d73bf8c2f550f7821ea1ca78bfc01ba Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Wed, 5 Jan 2022 16:25:31 +0800
Subject: [PATCH 40/73] Update constants and documentation after release of
2.0.206
---
h2/src/docsrc/html/changelog.html | 14 ++++++++++++--
h2/src/docsrc/html/download-archive.html | 8 ++++++++
h2/src/main/org/h2/engine/Constants.java | 4 ++--
h2/src/test/org/h2/samples/newsfeed.sql | 1 +
4 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html
index 3ee3651fa7..97471848ec 100644
--- a/h2/src/docsrc/html/changelog.html
+++ b/h2/src/docsrc/html/changelog.html
@@ -21,6 +21,10 @@ Change Log
Next Version (unreleased)
+- PR #3323: Tokenize SQL before parsing and preserve tokens for recompilation
+
+- PR #3320: Add Servlet 5-compatible servlet for H2 Console
+
- Issue #918: Parser fails recognising set operations in correlated subqueries
- Issue #2050: PostgreSQL with recursive fail with union in the final query
@@ -39,14 +43,20 @@
Next Version (unreleased)
- PR #3305: Add UNIQUE(VALUE) and remove some non-standard keywords
-- Issue #3297: Unexpected GROUP BY results with indexed IGNORECASE column
-
- PR #3299: Remove useless StringBuilder.toString() call
- PR #3298: Delete unused sqlTypes array
+Version 2.0.206 (2022-01-04)
+
+- Issue #3322: Create linked table fails when the table contains a Geometry with a data type specified
+
+- Issue #3297: Unexpected GROUP BY results with indexed IGNORECASE column
+
+
+
Version 2.0.204 (2021-12-21)
- Issue #3291: Add Legacy and Strict modes
diff --git a/h2/src/docsrc/html/download-archive.html b/h2/src/docsrc/html/download-archive.html
index 55adba8b20..b20fe68785 100644
--- a/h2/src/docsrc/html/download-archive.html
+++ b/h2/src/docsrc/html/download-archive.html
@@ -28,6 +28,14 @@
Distribution
+| 2.0.206 |
+Windows Installer |
+Platform-Independent Zip |
+
+| 2.0.204 |
+Windows Installer |
+Platform-Independent Zip |
+
| 1.4.202 |
Windows Installer |
Platform-Independent Zip |
diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java
index 26d8590e4b..a3f0fb86f8 100644
--- a/h2/src/main/org/h2/engine/Constants.java
+++ b/h2/src/main/org/h2/engine/Constants.java
@@ -20,7 +20,7 @@ public class Constants {
/**
* The build date of the last stable release.
*/
- public static final String BUILD_DATE_STABLE = "2021-12-21";
+ public static final String BUILD_DATE_STABLE = "2022-01-04";
/**
* Sequential version number. Even numbers are used for official releases,
@@ -31,7 +31,7 @@ public class Constants {
/**
* The build id of the last stable release.
*/
- public static final int BUILD_ID_STABLE = 204;
+ public static final int BUILD_ID_STABLE = 206;
/**
* Whether this is a snapshot version.
diff --git a/h2/src/test/org/h2/samples/newsfeed.sql b/h2/src/test/org/h2/samples/newsfeed.sql
index fb917dcd49..56cb2ad9cc 100644
--- a/h2/src/test/org/h2/samples/newsfeed.sql
+++ b/h2/src/test/org/h2/samples/newsfeed.sql
@@ -7,6 +7,7 @@
CREATE TABLE VERSION(ID INT PRIMARY KEY, VERSION VARCHAR, CREATED VARCHAR);
INSERT INTO VERSION VALUES
+(153, '2.0.206', '2022-01-04'),
(152, '2.0.204', '2021-12-21'),
(151, '2.0.202', '2021-11-25'),
(150, '1.4.200', '2019-10-14'),
From 27b97c94038233af4912ed5a1f8ed4be24bfe8b0 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Wed, 5 Jan 2022 17:01:35 +0800
Subject: [PATCH 41/73] Don't use sysIdentifier() in
CurrentGeneralValueSpecification
---
.../CurrentGeneralValueSpecification.java | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java b/h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java
index 429702359b..ca76fa7e4c 100644
--- a/h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java
+++ b/h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java
@@ -5,12 +5,14 @@
*/
package org.h2.expression.function;
+import org.h2.engine.Database;
import org.h2.engine.SessionLocal;
import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Operation0;
import org.h2.message.DbException;
import org.h2.util.HasSQL;
import org.h2.util.ParserUtil;
+import org.h2.util.StringUtils;
import org.h2.value.TypeInfo;
import org.h2.value.Value;
import org.h2.value.ValueNull;
@@ -88,16 +90,24 @@ public Value getValue(SessionLocal session) {
}
break;
}
- case CURRENT_ROLE:
- s = session.getDatabase().sysIdentifier(session.getDatabase().getPublicRole().getName());
+ case CURRENT_ROLE: {
+ Database db = session.getDatabase();
+ s = db.getPublicRole().getName();
+ if (db.getSettings().databaseToLower) {
+ s = StringUtils.toLowerEnglish(s);
+ }
break;
+ }
case CURRENT_SCHEMA:
s = session.getCurrentSchemaName();
break;
case CURRENT_USER:
case SESSION_USER:
case SYSTEM_USER:
- s = session.getDatabase().sysIdentifier(session.getUser().getName());
+ s = session.getUser().getName();
+ if (session.getDatabase().getSettings().databaseToLower) {
+ s = StringUtils.toLowerEnglish(s);
+ }
break;
default:
throw DbException.getInternalError("specification=" + specification);
From 50475fc01d9a9d48603ce4850e0e58d6a899127d Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Wed, 5 Jan 2022 17:02:49 +0800
Subject: [PATCH 42/73] Fix and improve Database.isUpperSysIdentifier()
---
h2/src/main/org/h2/engine/Database.java | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java
index ab839677c0..f7d6958c4e 100644
--- a/h2/src/main/org/h2/engine/Database.java
+++ b/h2/src/main/org/h2/engine/Database.java
@@ -2340,9 +2340,20 @@ private static boolean isUpperSysIdentifier(String upperName) {
if (l == 0) {
return false;
}
- for (int i = 0; i < l; i++) {
- int ch = upperName.charAt(i);
- if (ch < 'A' || ch > 'Z' && ch != '_') {
+ char c = upperName.charAt(0);
+ if (c < 'A' || c > 'Z') {
+ return false;
+ }
+ l--;
+ for (int i = 1; i < l; i++) {
+ c = upperName.charAt(i);
+ if ((c < 'A' || c > 'Z') && c != '_') {
+ return false;
+ }
+ }
+ if (l > 0) {
+ c = upperName.charAt(l);
+ if (c < 'A' || c > 'Z') {
return false;
}
}
From 2357b031787a29b620770c04569fdd621614c6dd Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Wed, 5 Jan 2022 18:35:39 +0800
Subject: [PATCH 43/73] Fix ALTER TABLE ADD for identity and generated columns
---
h2/src/docsrc/html/changelog.html | 2 +
.../h2/command/ddl/AlterTableAlterColumn.java | 59 ++++++++++---------
.../org/h2/test/scripts/ddl/alterTableAdd.sql | 25 ++++++++
3 files changed, 57 insertions(+), 29 deletions(-)
diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html
index 97471848ec..e8fb6df89e 100644
--- a/h2/src/docsrc/html/changelog.html
+++ b/h2/src/docsrc/html/changelog.html
@@ -21,6 +21,8 @@ Change Log
Next Version (unreleased)
+- Issue #3321: Insert Primary Key after import CSV Data does not work
+
- PR #3323: Tokenize SQL before parsing and preserve tokens for recompilation
- PR #3320: Add Servlet 5-compatible servlet for H2 Console
diff --git a/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java b/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java
index 5fbdd9a545..ebb8baa2ef 100644
--- a/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java
+++ b/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java
@@ -247,7 +247,7 @@ public long update() {
throw DbException.get(ErrorCode.CANNOT_DROP_LAST_COLUMN, columnsToRemove.get(0).getTraceSQL());
}
table.dropMultipleColumnsConstraintsAndIndexes(session, columnsToRemove);
- copyData(table);
+ copyData(table, null, false);
break;
}
case CommandInterface.ALTER_TABLE_ALTER_COLUMN_SELECTIVITY: {
@@ -327,10 +327,6 @@ private void removeSequence(Table table, Sequence sequence) {
}
}
- private void copyData(Table table) {
- copyData(table, null, false);
- }
-
private void copyData(Table table, ArrayList sequences, boolean createConstraints) {
if (table.isTemporary()) {
throw DbException.getUnsupportedException("TEMP TABLE");
@@ -458,34 +454,30 @@ private Table cloneTableStructure(Table table, Column[] columns, Database db,
Table newTable = getSchema().createTable(data);
newTable.setComment(table.getComment());
String newTableSQL = newTable.getCreateSQLForMeta();
- StringBuilder columnList = new StringBuilder();
+ StringBuilder columnNames = new StringBuilder();
+ StringBuilder columnValues = new StringBuilder();
for (Column nc : newColumns) {
- if (columnList.length() > 0) {
- columnList.append(", ");
+ if (nc.isGenerated()) {
+ continue;
}
switch (type) {
case CommandInterface.ALTER_TABLE_ADD_COLUMN:
if (columnsToAdd != null && columnsToAdd.contains(nc)) {
if (usingExpression != null) {
- usingExpression.getUnenclosedSQL(columnList, HasSQL.DEFAULT_SQL_FLAGS);
- } else {
- Expression def = nc.getDefaultExpression();
- if (def == null) {
- columnList.append("NULL");
- } else {
- def.getUnenclosedSQL(columnList, HasSQL.DEFAULT_SQL_FLAGS);
- }
+ usingExpression.getUnenclosedSQL(addColumn(nc, columnNames, columnValues),
+ HasSQL.DEFAULT_SQL_FLAGS);
}
continue;
}
break;
case CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE:
if (nc.equals(newColumn) && usingExpression != null) {
- usingExpression.getUnenclosedSQL(columnList, HasSQL.DEFAULT_SQL_FLAGS);
+ usingExpression.getUnenclosedSQL(addColumn(nc, columnNames, columnValues),
+ HasSQL.DEFAULT_SQL_FLAGS);
continue;
}
}
- nc.getSQL(columnList, HasSQL.DEFAULT_SQL_FLAGS);
+ nc.getSQL(addColumn(nc, columnNames, columnValues), HasSQL.DEFAULT_SQL_FLAGS);
}
String newTableName = newTable.getName();
Schema newTableSchema = newTable.getSchema();
@@ -551,24 +543,22 @@ private Table cloneTableStructure(Table table, Column[] columns, Database db,
}
}
}
- StringBuilder buff = new StringBuilder();
- buff.append("INSERT INTO ");
- newTable.getSQL(buff, HasSQL.DEFAULT_SQL_FLAGS);
- buff.append(" SELECT ");
- if (columnList.length() == 0) {
+ StringBuilder builder = newTable.getSQL(new StringBuilder(128).append("INSERT INTO "), //
+ HasSQL.DEFAULT_SQL_FLAGS)
+ .append('(').append(columnNames).append(") OVERRIDING SYSTEM VALUE SELECT ");
+ if (columnValues.length() == 0) {
// special case: insert into test select * from
- buff.append('*');
+ builder.append('*');
} else {
- buff.append(columnList);
+ builder.append(columnValues);
}
- buff.append(" FROM ");
- table.getSQL(buff, HasSQL.DEFAULT_SQL_FLAGS);
+ table.getSQL(builder.append(" FROM "), HasSQL.DEFAULT_SQL_FLAGS);
try {
- execute(buff.toString());
+ execute(builder.toString());
} catch (Throwable t) {
// data was not inserted due to data conversion error or some
// unexpected reason
- StringBuilder builder = new StringBuilder("DROP TABLE ");
+ builder = new StringBuilder("DROP TABLE ");
newTable.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS);
execute(builder.toString());
throw t;
@@ -592,6 +582,17 @@ private Table cloneTableStructure(Table table, Column[] columns, Database db,
return newTable;
}
+ private static StringBuilder addColumn(Column column, StringBuilder columnNames, StringBuilder columnValues) {
+ if (columnNames.length() > 0) {
+ columnNames.append(", ");
+ }
+ column.getSQL(columnNames, HasSQL.DEFAULT_SQL_FLAGS);
+ if (columnValues.length() > 0) {
+ columnValues.append(", ");
+ }
+ return columnValues;
+ }
+
/**
* Check that all views and other dependent objects.
*/
diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql
index 590de2a217..9f00abb42f 100644
--- a/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql
+++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql
@@ -368,3 +368,28 @@ ALTER TABLE TEST ADD UNIQUE (VALUE);
DROP TABLE TEST;
> ok
+
+CREATE TABLE TEST(A INT, B INT) AS VALUES (3, 4);
+> ok
+
+ALTER TABLE TEST ADD G INT GENERATED ALWAYS AS (A + B);
+> ok
+
+ALTER TABLE TEST ADD ID BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY FIRST;
+> ok
+
+ALTER TABLE TEST ADD C INT AFTER B;
+> ok
+
+INSERT INTO TEST(A, B) VALUES (5, 6);
+> update count: 1
+
+TABLE TEST;
+> ID A B C G
+> -- - - ---- --
+> 1 3 4 null 7
+> 2 5 6 null 11
+> rows: 2
+
+DROP TABLE TEST;
+> ok
From 07aabef8edd4b8eb2cd50090804ad1b732c30350 Mon Sep 17 00:00:00 2001
From: Evgenij Ryazanov
Date: Thu, 6 Jan 2022 12:43:59 +0800
Subject: [PATCH 44/73] Store database settings as fields in Tokenizer
---
h2/src/main/org/h2/command/Parser.java | 6 +-
h2/src/main/org/h2/command/Tokenizer.java | 358 +++++++++-------------
2 files changed, 142 insertions(+), 222 deletions(-)
diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java
index 005b36e3d5..ecdbfe28f8 100644
--- a/h2/src/main/org/h2/command/Parser.java
+++ b/h2/src/main/org/h2/command/Parser.java
@@ -5901,10 +5901,8 @@ private void initialize(String sql, ArrayList tokens, boolean stopOnClose
sql = "";
}
sqlCommand = sql;
- this.tokens = tokens == null
- ? Tokenizer.tokenize(sql, database, identifiersToUpper, identifiersToLower, nonKeywords,
- stopOnCloseParen)
- : tokens;
+ this.tokens = tokens == null ? new Tokenizer(database, identifiersToUpper, identifiersToLower, nonKeywords)
+ .tokenize(sql, stopOnCloseParen) : tokens;
resetTokenIndex();
}
diff --git a/h2/src/main/org/h2/command/Tokenizer.java b/h2/src/main/org/h2/command/Tokenizer.java
index 540ca0ff84..132804d299 100644
--- a/h2/src/main/org/h2/command/Tokenizer.java
+++ b/h2/src/main/org/h2/command/Tokenizer.java
@@ -144,11 +144,22 @@
*/
public final class Tokenizer {
- private Tokenizer() {
+ private final CastDataProvider provider;
+
+ private final boolean identifiersToUpper;
+
+ private final boolean identifiersToLower;
+
+ private final BitSet nonKeywords;
+
+ Tokenizer(CastDataProvider provider, boolean identifiersToUpper, boolean identifiersToLower, BitSet nonKeywords) {
+ this.provider = provider;
+ this.identifiersToUpper = identifiersToUpper;
+ this.identifiersToLower = identifiersToLower;
+ this.nonKeywords = nonKeywords;
}
- static ArrayList tokenize(String sql, CastDataProvider provider, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, boolean stopOnCloseParen) {
+ ArrayList tokenize(String sql, boolean stopOnCloseParen) {
ArrayList tokens = new ArrayList<>();
int end = sql.length() - 1;
boolean foundUnicode = false;
@@ -172,13 +183,11 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
throw DbException.getSyntaxError(sql, tokenStart);
case '"':
case '`':
- i = readQuotedIdentifier(sql, end, identifiersToUpper, identifiersToLower, tokenStart, i, c, false,
- tokens);
+ i = readQuotedIdentifier(sql, end, tokenStart, i, c, false, tokens);
continue loop;
case '#':
if (provider.getMode().supportPoundSymbolForColumnNames) {
- i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i, c,
- tokens);
+ i = readIdentifier(sql, end, tokenStart, i, c, tokens);
continue loop;
}
throw DbException.getSyntaxError(sql, tokenStart);
@@ -212,7 +221,7 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
}
throw DbException.getSyntaxError(sql, tokenStart);
case '\'':
- i = readCharacterString(sql, provider, tokenStart, end, i, false, tokens);
+ i = readCharacterString(sql, tokenStart, end, i, false, tokens);
continue loop;
case '(':
token = new Token.KeywordToken(tokenStart, OPEN_PAREN);
@@ -351,154 +360,127 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
break;
case 'A':
case 'a':
- i = readA(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readA(sql, end, tokenStart, i, tokens);
continue loop;
case 'B':
case 'b':
- i = readB(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readB(sql, end, tokenStart, i, tokens);
continue loop;
case 'C':
case 'c':
- i = readC(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readC(sql, end, tokenStart, i, tokens);
continue loop;
case 'D':
case 'd':
- i = readD(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readD(sql, end, tokenStart, i, tokens);
continue loop;
case 'E':
case 'e':
- i = readE(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readE(sql, end, tokenStart, i, tokens);
continue loop;
case 'F':
case 'f':
- i = readF(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readF(sql, end, tokenStart, i, tokens);
continue loop;
case 'G':
case 'g':
- i = readG(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readG(sql, end, tokenStart, i, tokens);
continue loop;
case 'H':
case 'h':
- i = readH(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readH(sql, end, tokenStart, i, tokens);
continue loop;
case 'I':
case 'i':
- i = readI(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readI(sql, end, tokenStart, i, tokens);
continue loop;
case 'J':
case 'j':
- i = readJ(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readJ(sql, end, tokenStart, i, tokens);
continue loop;
case 'K':
case 'k':
- i = readK(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readK(sql, end, tokenStart, i, tokens);
continue loop;
case 'L':
case 'l':
- i = readL(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readL(sql, end, tokenStart, i, tokens);
continue loop;
case 'M':
case 'm':
- i = readM(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readM(sql, end, tokenStart, i, tokens);
continue loop;
case 'N':
case 'n':
if (i < end && sql.charAt(i + 1) == '\'') {
- i = readCharacterString(sql, provider, tokenStart, end, i + 1, false, tokens);
+ i = readCharacterString(sql, tokenStart, end, i + 1, false, tokens);
} else {
- i = readN(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readN(sql, end, tokenStart, i, tokens);
}
continue loop;
case 'O':
case 'o':
- i = readO(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readO(sql, end, tokenStart, i, tokens);
continue loop;
case 'P':
case 'p':
- i = readP(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readP(sql, end, tokenStart, i, tokens);
continue loop;
case 'Q':
case 'q':
- i = readQ(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readQ(sql, end, tokenStart, i, tokens);
continue loop;
case 'R':
case 'r':
- i = readR(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readR(sql, end, tokenStart, i, tokens);
continue loop;
case 'S':
case 's':
- i = readS(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readS(sql, end, tokenStart, i, tokens);
continue loop;
case 'T':
case 't':
- i = readT(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readT(sql, end, tokenStart, i, tokens);
continue loop;
case 'U':
case 'u':
if (i + 1 < end && sql.charAt(i + 1) == '&') {
char c3 = sql.charAt(i + 2);
if (c3 == '"') {
- i = readQuotedIdentifier(sql, end, identifiersToUpper, identifiersToLower, tokenStart, //
- i + 2, '"', true, tokens);
+ i = readQuotedIdentifier(sql, end, tokenStart, i + 2, '"', true, tokens);
foundUnicode = true;
continue loop;
} else if (c3 == '\'') {
- i = readCharacterString(sql, provider, tokenStart, end, i + 2, true, tokens);
+ i = readCharacterString(sql, tokenStart, end, i + 2, true, tokens);
foundUnicode = true;
continue loop;
}
}
- i = readU(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readU(sql, end, tokenStart, i, tokens);
continue loop;
case 'V':
case 'v':
- i = readV(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readV(sql, end, tokenStart, i, tokens);
continue loop;
case 'W':
case 'w':
- i = readW(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readW(sql, end, tokenStart, i, tokens);
continue loop;
case 'X':
case 'x':
if (i < end && sql.charAt(i + 1) == '\'') {
- i = readBinaryString(sql, provider, tokenStart, end, i + 1, tokens);
+ i = readBinaryString(sql, tokenStart, end, i + 1, tokens);
} else {
- i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i, c,
- tokens);
+ i = readIdentifier(sql, end, tokenStart, i, c, tokens);
}
continue loop;
case 'Y':
case 'y':
- i = readY(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = readY(sql, end, tokenStart, i, tokens);
continue loop;
case 'Z':
case 'z':
- i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i, c,
- tokens);
+ i = readIdentifier(sql, end, tokenStart, i, c, tokens);
continue loop;
case '[':
if (provider.getMode().squareBracketQuotedNames) {
@@ -516,8 +498,7 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
token = new Token.KeywordToken(tokenStart, CLOSE_BRACKET);
break;
case '_':
- i = read_(sql, provider, end, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, i,
- tokens);
+ i = read_(sql, end, tokenStart, i, tokens);
continue loop;
case '{':
token = new Token.KeywordToken(tokenStart, OPEN_BRACE);
@@ -544,8 +525,7 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
continue loop;
}
if (Character.isJavaIdentifierStart(cp)) {
- i = readIdentifier(sql, provider, end, identifiersToUpper, identifiersToLower, tokenStart, i,
- cp, tokens);
+ i = readIdentifier(sql, end, tokenStart, i, cp, tokens);
continue loop;
}
throw DbException.getSyntaxError(sql, tokenStart);
@@ -561,20 +541,17 @@ static ArrayList tokenize(String sql, CastDataProvider provider, boolean
return tokens;
}
- private static int readIdentifier(String sql, CastDataProvider provider, int end, boolean identifiersToUpper, //
- boolean identifiersToLower, int tokenStart, int i, int cp, ArrayList tokens) {
+ private int readIdentifier(String sql, int end, int tokenStart, int i, int cp, ArrayList tokens) {
if (cp >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
i++;
}
- int endIndex = findIdentifierEnd(sql, provider, end, i + Character.charCount(cp) - 1);
- tokens.add(new Token.IdentifierToken(tokenStart,
- extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), false, false));
+ int endIndex = findIdentifierEnd(sql, end, i + Character.charCount(cp) - 1);
+ tokens.add(new Token.IdentifierToken(tokenStart, extractIdentifier(sql, tokenStart, endIndex), false, false));
return endIndex;
}
- private static int readA(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readA(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (length == 2) {
@@ -596,22 +573,18 @@ private static int readA(String sql, CastDataProvider provider, int end, boolean
type = IDENTIFIER;
}
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readB(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readB(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("BETWEEN", sql, tokenStart, length) ? BETWEEN : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readC(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readC(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("CASE", sql, tokenStart, length)) {
@@ -629,8 +602,7 @@ private static int readC(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
private static int getTokenTypeCurrent(String s, int tokenStart, int length) {
@@ -676,9 +648,8 @@ private static boolean eqCurrent(String expected, String s, int start, int lengt
return true;
}
- private static int readD(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readD(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("DAY", sql, tokenStart, length)) {
@@ -690,13 +661,11 @@ private static int readD(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readE(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readE(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("ELSE", sql, tokenStart, length)) {
@@ -710,13 +679,11 @@ private static int readE(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readF(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readF(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("FETCH", sql, tokenStart, length)) {
@@ -734,22 +701,18 @@ private static int readF(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readG(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readG(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("GROUP", sql, tokenStart, length) ? GROUP : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readH(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readH(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("HAVING", sql, tokenStart, length)) {
@@ -759,13 +722,11 @@ private static int readH(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readI(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readI(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (length == 2) {
@@ -793,31 +754,25 @@ private static int readI(String sql, CastDataProvider provider, int end, boolean
type = IDENTIFIER;
}
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readJ(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readJ(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("JOIN", sql, tokenStart, length) ? JOIN : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readK(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readK(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("KEY", sql, tokenStart, length) ? KEY : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readL(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readL(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("LEFT", sql, tokenStart, length)) {
@@ -833,13 +788,11 @@ private static int readL(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readM(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readM(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("MINUS", sql, tokenStart, length)) {
@@ -851,13 +804,11 @@ private static int readM(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readN(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readN(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("NOT", sql, tokenStart, length)) {
@@ -869,13 +820,11 @@ private static int readN(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readO(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readO(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (length == 2) {
@@ -898,31 +847,25 @@ private static int readO(String sql, CastDataProvider provider, int end, boolean
type = IDENTIFIER;
}
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readP(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readP(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("PRIMARY", sql, tokenStart, length) ? PRIMARY : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readQ(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readQ(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("QUALIFY", sql, tokenStart, length) ? QUALIFY : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readR(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readR(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("RIGHT", sql, tokenStart, length)) {
@@ -934,13 +877,11 @@ private static int readR(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readS(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readS(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("SECOND", sql, tokenStart, length)) {
@@ -960,13 +901,11 @@ private static int readS(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readT(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readT(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (length == 2) {
@@ -980,13 +919,11 @@ private static int readT(String sql, CastDataProvider provider, int end, boolean
type = IDENTIFIER;
}
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readU(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readU(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("UESCAPE", sql, tokenStart, length)) {
@@ -1004,13 +941,11 @@ private static int readU(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readV(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readV(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("VALUE", sql, tokenStart, length)) {
@@ -1020,13 +955,11 @@ private static int readV(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readW(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readW(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type;
if (eq("WHEN", sql, tokenStart, length)) {
@@ -1040,38 +973,29 @@ private static int readW(String sql, CastDataProvider provider, int end, boolean
} else {
type = IDENTIFIER;
}
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readY(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int readY(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int length = endIndex - tokenStart;
int type = eq("YEAR", sql, tokenStart, length) ? YEAR : IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int read_(String sql, CastDataProvider provider, int end, boolean identifiersToUpper,
- boolean identifiersToLower, BitSet nonKeywords, int tokenStart, int i, ArrayList tokens) {
- int endIndex = findIdentifierEnd(sql, provider, end, i);
+ private int read_(String sql, int end, int tokenStart, int i, ArrayList tokens) {
+ int endIndex = findIdentifierEnd(sql, end, i);
int type = endIndex - tokenStart == 7 && "_ROWID_".regionMatches(true, 1, sql, tokenStart + 1, 6) ? _ROWID_
: IDENTIFIER;
- return readIdentifierOrKeyword(sql, identifiersToUpper, identifiersToLower, nonKeywords, tokenStart, tokens,
- endIndex, type);
+ return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type);
}
- private static int readIdentifierOrKeyword(String sql, boolean identifiersToUpper, boolean identifiersToLower,
- BitSet nonKeywords, int tokenStart, ArrayList tokens, int endIndex, int type) {
+ private int readIdentifierOrKeyword(String sql, int tokenStart, ArrayList tokens, int endIndex, int type) {
Token token;
if (type == IDENTIFIER) {
- token = new Token.IdentifierToken(tokenStart,
- extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex), //
- false, false);
+ token = new Token.IdentifierToken(tokenStart, extractIdentifier(sql, tokenStart, endIndex), false, false);
} else if (nonKeywords != null && nonKeywords.get(type)) {
- token = new Token.KeywordOrIdentifierToken(tokenStart, type,
- extractIdentifier(sql, identifiersToUpper, identifiersToLower, tokenStart, endIndex));
+ token = new Token.KeywordOrIdentifierToken(tokenStart, type, extractIdentifier(sql, tokenStart, endIndex));
} else {
token = new Token.KeywordToken(tokenStart, type);
}
@@ -1091,7 +1015,7 @@ private static boolean eq(String expected, String s, int start, int length) {
return true;
}
- private static int findIdentifierEnd(String sql, CastDataProvider provider, int end, int i) {
+ private int findIdentifierEnd(String sql, int end, int i) {
i++;
for (;;) {
int cp;
@@ -1104,13 +1028,12 @@ private static int findIdentifierEnd(String sql, CastDataProvider provider, int
return i;
}
- private static String extractIdentifier(String sql, boolean identifiersToUpper, boolean identifiersToLower,
- int beginIndex, int endIndex) {
- return convertCase(identifiersToUpper, identifiersToLower, sql.substring(beginIndex, endIndex));
+ private String extractIdentifier(String sql, int beginIndex, int endIndex) {
+ return convertCase(sql.substring(beginIndex, endIndex));
}
- private static int readQuotedIdentifier(String sql, int end, boolean identifiersToUpper, //
- boolean identifiersToLower, int tokenStart, int i, char c, boolean unicode, ArrayList tokens) {
+ private int readQuotedIdentifier(String sql, int end, int tokenStart, int i, char c, boolean unicode,
+ ArrayList tokens) {
int identifierEnd = sql.indexOf(c, ++i);
if (identifierEnd < 0) {
throw DbException.getSyntaxError(sql, tokenStart);
@@ -1130,13 +1053,13 @@ private static int readQuotedIdentifier(String sql, int end, boolean identifiers
s = builder.toString();
}
if (c == '`') {
- s = convertCase(identifiersToUpper, identifiersToLower, s);
+ s = convertCase(s);
}
tokens.add(new Token.IdentifierToken(tokenStart, s, true, unicode));
return i;
}
- private static String convertCase(boolean identifiersToUpper, boolean identifiersToLower, String s) {
+ private String convertCase(String s) {
if (identifiersToUpper) {
s = StringUtils.toUpperEnglish(s);
} else if (identifiersToLower) {
@@ -1145,8 +1068,7 @@ private static String convertCase(boolean identifiersToUpper, boolean identifier
return s;
}
- private static int readBinaryString(String sql, CastDataProvider provider, int tokenStart, int end, int i,
- ArrayList tokens) {
+ private static int readBinaryString(String sql, int tokenStart, int end, int i, ArrayList tokens) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
int stringEnd;
do {
@@ -1161,8 +1083,8 @@ private static int readBinaryString(String sql, CastDataProvider provider, int t
return i;
}
- private static int readCharacterString(String sql, CastDataProvider provider, int tokenStart, int end, int i,
- boolean unicode, ArrayList tokens) {
+ private static int readCharacterString(String sql, int tokenStart, int end, int i, boolean unicode,
+ ArrayList