From 472a9c663a464727d75310e59ba3f6b3fdc99401 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Mon, 17 Jan 2022 23:36:47 +0800 Subject: [PATCH 01/93] Use secure parser in H2AuthConfigXml to avoid future reports --- h2/src/docsrc/html/changelog.html | 2 +- .../org/h2/security/auth/H2AuthConfigXml.java | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 23c1e63e38..9750b5f4af 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,7 +21,7 @@

Change Log

Next Version (unreleased)

diff --git a/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java b/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java index b1f6888d59..fb1eb16f84 100644 --- a/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java +++ b/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java @@ -7,11 +7,16 @@ import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.net.URL; + +import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; + import org.xml.sax.Attributes; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -67,6 +72,11 @@ public void endElement(String uri, String localName, String qName) throws SAXExc } } + @Override + public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException { + return new InputSource(new StringReader("")); + } + private static String getMandatoryAttributeValue(String attributeName, Attributes attributes) throws SAXException { String attributeValue=attributes.getValue(attributeName); if (attributeValue==null || attributeValue.trim().equals("")) { @@ -120,7 +130,13 @@ public static H2AuthConfig parseFrom(URL url) */ public static H2AuthConfig parseFrom(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException { - SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + SAXParser saxParser = spf.newSAXParser(); H2AuthConfigXml xmlHandler = new H2AuthConfigXml(); saxParser.parse(inputStream, xmlHandler); return xmlHandler.getResult(); From 47af4ae67327eeba9800f6109bfa914bce1514d4 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 17 Jan 2022 11:01:46 -0500 Subject: [PATCH 02/93] Version advancement --- h2/pom.xml | 2 +- h2/src/main/org/h2/engine/Constants.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/h2/pom.xml b/h2/pom.xml index a0c0085569..7a29f41f62 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -4,7 +4,7 @@ com.h2database h2 - 2.1.210 + 2.1.219-SNAPSHOT jar H2 Database Engine https://h2database.com diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index d71cf6b656..9ba74005ca 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -21,12 +21,12 @@ public class Constants { * Sequential version number. Even numbers are used for official releases, * odd numbers are used for development builds. */ - public static final int BUILD_ID = 210; + public static final int BUILD_ID = 219; /** * Whether this is a snapshot version. */ - public static final boolean BUILD_SNAPSHOT = false; + public static final boolean BUILD_SNAPSHOT = true; /** * If H2 is compiled to be included in a product, this should be set to From 59fa857fd7b5fdf2cc912eae779ebff4353bd9a5 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 17 Jan 2022 11:02:27 -0500 Subject: [PATCH 03/93] release build adjustments --- h2/build.sh | 2 +- h2/src/tools/org/h2/build/Build.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/h2/build.sh b/h2/build.sh index 558a7945ab..769262d58b 100755 --- a/h2/build.sh +++ b/h2/build.sh @@ -15,4 +15,4 @@ if [ "$1" = "clean" ] ; then rm -rf temp bin ; fi if [ ! -d "temp" ] ; then mkdir temp ; fi if [ ! -d "bin" ] ; then mkdir bin ; fi "$JAVA_HOME/bin/javac" -sourcepath src/tools -d bin src/tools/org/h2/build/*.java -"$JAVA_HOME/bin/java" -Xmx512m -cp "bin:$JAVA_HOME/lib/tools.jar:temp" org.h2.build.Build $@ +"$JAVA_HOME/bin/java" -Xmx1g -cp "bin:$JAVA_HOME/lib/tools.jar:temp" org.h2.build.Build $@ diff --git a/h2/src/tools/org/h2/build/Build.java b/h2/src/tools/org/h2/build/Build.java index 7d23858a3b..db504f165f 100644 --- a/h2/src/tools/org/h2/build/Build.java +++ b/h2/src/tools/org/h2/build/Build.java @@ -611,7 +611,6 @@ public void javadocImpl() { // need to be disabled if not enough memory File.pathSeparator + "src/test" + File.pathSeparator + "src/tools", - "-Xdoclint:all,-missing", "-noindex", "-d", "docs/javadocImpl2", "-classpath", javaToolsJar + @@ -652,6 +651,7 @@ public void javadocImpl() { javadoc("-sourcepath", "src/main" + File.pathSeparator + "src/test" + File.pathSeparator + "src/tools", + "-Xdoclint:all,-missing", "-d", "docs/javadoc", "-classpath", javaToolsJar + File.pathSeparator + "ext/slf4j-api-" + SLF4J_VERSION + ".jar" + From e54d057f12ed4d5c70a8c20494f3eeeaa8318b04 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 18 Jan 2022 17:40:32 +0800 Subject: [PATCH 04/93] Bump minor version --- h2/pom.xml | 2 +- h2/src/main/org/h2/engine/Constants.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/h2/pom.xml b/h2/pom.xml index 7a29f41f62..36fdd1e2a7 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -4,7 +4,7 @@ com.h2database h2 - 2.1.219-SNAPSHOT + 2.2.219-SNAPSHOT jar H2 Database Engine https://h2database.com diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index 9ba74005ca..00b986728b 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -78,7 +78,7 @@ public class Constants { /** * The minor version of this database. */ - public static final int VERSION_MINOR = 1; + public static final int VERSION_MINOR = 2; /** * The lock mode that means no locking is used at all. From 43e0d798f1f05b67dc497340390ea8f92c806c07 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 18 Jan 2022 17:48:58 +0800 Subject: [PATCH 05/93] Remove abandoned Java to C converter --- h2/src/tools/org/h2/java/ClassObj.java | 463 ----- h2/src/tools/org/h2/java/Expr.java | 736 ------- h2/src/tools/org/h2/java/Ignore.java | 13 - h2/src/tools/org/h2/java/JavaParser.java | 1848 ----------------- h2/src/tools/org/h2/java/Local.java | 14 - h2/src/tools/org/h2/java/Statement.java | 504 ----- h2/src/tools/org/h2/java/Test.java | 92 - h2/src/tools/org/h2/java/TestApp.java | 58 - h2/src/tools/org/h2/java/io/PrintStream.java | 24 - h2/src/tools/org/h2/java/io/package.html | 14 - h2/src/tools/org/h2/java/lang/Integer.java | 61 - h2/src/tools/org/h2/java/lang/Long.java | 62 - h2/src/tools/org/h2/java/lang/Math.java | 24 - h2/src/tools/org/h2/java/lang/Object.java | 27 - h2/src/tools/org/h2/java/lang/String.java | 222 -- .../tools/org/h2/java/lang/StringBuilder.java | 66 - h2/src/tools/org/h2/java/lang/System.java | 77 - h2/src/tools/org/h2/java/lang/package.html | 14 - h2/src/tools/org/h2/java/package.html | 14 - h2/src/tools/org/h2/java/util/Arrays.java | 74 - h2/src/tools/org/h2/java/util/package.html | 14 - 21 files changed, 4421 deletions(-) delete mode 100644 h2/src/tools/org/h2/java/ClassObj.java delete mode 100644 h2/src/tools/org/h2/java/Expr.java delete mode 100644 h2/src/tools/org/h2/java/Ignore.java delete mode 100644 h2/src/tools/org/h2/java/JavaParser.java delete mode 100644 h2/src/tools/org/h2/java/Local.java delete mode 100644 h2/src/tools/org/h2/java/Statement.java delete mode 100644 h2/src/tools/org/h2/java/Test.java delete mode 100644 h2/src/tools/org/h2/java/TestApp.java delete mode 100644 h2/src/tools/org/h2/java/io/PrintStream.java delete mode 100644 h2/src/tools/org/h2/java/io/package.html delete mode 100644 h2/src/tools/org/h2/java/lang/Integer.java delete mode 100644 h2/src/tools/org/h2/java/lang/Long.java delete mode 100644 h2/src/tools/org/h2/java/lang/Math.java delete mode 100644 h2/src/tools/org/h2/java/lang/Object.java delete mode 100644 h2/src/tools/org/h2/java/lang/String.java delete mode 100644 h2/src/tools/org/h2/java/lang/StringBuilder.java delete mode 100644 h2/src/tools/org/h2/java/lang/System.java delete mode 100644 h2/src/tools/org/h2/java/lang/package.html delete mode 100644 h2/src/tools/org/h2/java/package.html delete mode 100644 h2/src/tools/org/h2/java/util/Arrays.java delete mode 100644 h2/src/tools/org/h2/java/util/package.html diff --git a/h2/src/tools/org/h2/java/ClassObj.java b/h2/src/tools/org/h2/java/ClassObj.java deleted file mode 100644 index 88a84beb2d..0000000000 --- a/h2/src/tools/org/h2/java/ClassObj.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * 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.java; - -import java.util.ArrayList; -import java.util.LinkedHashMap; - -/** - * A class or interface. - */ -public class ClassObj { - - /** - * The super class (null for java.lang.Object or primitive types). - */ - String superClassName; - - /** - * The list of interfaces that this class implements. - */ - ArrayList interfaceNames = new ArrayList<>(); - - - /** - * The fully qualified class name. - */ - String className; - - /** - * Whether this is an interface. - */ - boolean isInterface; - - /** - * Whether this class is public. - */ - boolean isPublic; - - /** - * Whether this is a primitive class (int, char,...) - */ - boolean isPrimitive; - - /** - * The primitive type (higher types are more complex) - */ - int primitiveType; - - /** - * The imported classes. - */ - ArrayList imports = new ArrayList<>(); - - /** - * The per-instance fields. - */ - LinkedHashMap instanceFields = - new LinkedHashMap<>(); - - /** - * The static fields of this class. - */ - LinkedHashMap staticFields = - new LinkedHashMap<>(); - - /** - * The methods. - */ - LinkedHashMap> methods = - new LinkedHashMap<>(); - - /** - * The list of native statements. - */ - ArrayList nativeCode = new ArrayList<>(); - - /** - * The class number. - */ - int id; - - /** - * Get the base type of this class. - */ - Type baseType; - - ClassObj() { - baseType = new Type(); - baseType.classObj = this; - } - - /** - * Add a method. - * - * @param method the method - */ - void addMethod(MethodObj method) { - ArrayList list = methods.get(method.name); - if (list == null) { - list = new ArrayList<>(); - methods.put(method.name, list); - } else { - // for overloaded methods - // method.name = method.name + "_" + (list.size() + 1); - } - list.add(method); - } - - /** - * Add an instance field. - * - * @param field the field - */ - void addInstanceField(FieldObj field) { - instanceFields.put(field.name, field); - } - - /** - * Add a static field. - * - * @param field the field - */ - void addStaticField(FieldObj field) { - staticFields.put(field.name, field); - } - - @Override - public String toString() { - if (isPrimitive) { - return "j" + className; - } - return JavaParser.toC(className); - } - - /** - * Get the method. - * - * @param find the method name in the source code - * @param args the parameters - * @return the method - */ - MethodObj getMethod(String find, ArrayList args) { - ArrayList list = methods.get(find); - if (list == null) { - throw new RuntimeException("Method not found: " + className + " " + find); - } - if (list.size() == 1) { - return list.get(0); - } - for (MethodObj m : list) { - if (!m.isVarArgs && m.parameters.size() != args.size()) { - continue; - } - boolean match = true; - int i = 0; - for (FieldObj f : m.parameters.values()) { - Expr a = args.get(i++); - Type t = a.getType(); - if (!t.equals(f.type)) { - match = false; - break; - } - } - if (match) { - return m; - } - } - throw new RuntimeException("Method not found: " + className); - } - - /** - * Get the field with the given name. - * - * @param name the field name - * @return the field - */ - FieldObj getField(String name) { - return instanceFields.get(name); - } - - @Override - public int hashCode() { - return className.hashCode(); - } - - @Override - public boolean equals(Object other) { - if (other instanceof ClassObj) { - ClassObj c = (ClassObj) other; - return c.className.equals(className); - } - return false; - } - -} - -/** - * A method. - */ -class MethodObj { - - /** - * Whether the last parameter is a var args parameter. - */ - boolean isVarArgs; - - /** - * Whether this method is static. - */ - boolean isStatic; - - /** - * Whether this method is private. - */ - boolean isPrivate; - - /** - * Whether this method is overridden. - */ - boolean isVirtual; - - /** - * Whether this method is to be ignored (using the Ignore annotation). - */ - boolean isIgnore; - - /** - * The name. - */ - String name; - - /** - * The statement block (if any). - */ - Statement block; - - /** - * The return type. - */ - Type returnType; - - /** - * The parameter list. - */ - LinkedHashMap parameters = - new LinkedHashMap<>(); - - /** - * Whether this method is final. - */ - boolean isFinal; - - /** - * Whether this method is public. - */ - boolean isPublic; - - /** - * Whether this method is native. - */ - boolean isNative; - - /** - * Whether this is a constructor. - */ - boolean isConstructor; - - @Override - public String toString() { - return name; - } - -} - -/** - * A field. - */ -class FieldObj { - - /** - * The type. - */ - Type type; - - /** - * Whether this is a variable or parameter. - */ - boolean isVariable; - - /** - * Whether this is a local field (not separately garbage collected). - */ - boolean isLocalField; - - /** - * The field name. - */ - String name; - - /** - * Whether this field is static. - */ - boolean isStatic; - - /** - * Whether this field is final. - */ - boolean isFinal; - - /** - * Whether this field is private. - */ - boolean isPrivate; - - /** - * Whether this field is public. - */ - boolean isPublic; - - /** - * Whether this method is to be ignored (using the Ignore annotation). - */ - boolean isIgnore; - - /** - * The initial value expression (may be null). - */ - Expr value; - - /** - * The class where this field is declared. - */ - ClassObj declaredClass; - - @Override - public String toString() { - return name; - } - -} - -/** - * A type. - */ -class Type { - - /** - * The class. - */ - ClassObj classObj; - - /** - * The array nesting level. 0 if not an array. - */ - int arrayLevel; - - /** - * Whether this is a var args parameter. - */ - boolean isVarArgs; - - /** - * Use ref-counting. - */ - boolean refCount = JavaParser.REF_COUNT; - - /** - * Whether this is a array or an non-primitive type. - * - * @return true if yes - */ - public boolean isObject() { - return arrayLevel > 0 || !classObj.isPrimitive; - } - - @Override - public String toString() { - return asString(); - } - - /** - * Get the C++ code. - * - * @return the C++ code - */ - public String asString() { - StringBuilder buff = new StringBuilder(); - for (int i = 0; i < arrayLevel; i++) { - if (refCount) { - buff.append("ptr< "); - } - buff.append("array< "); - } - if (refCount) { - if (!classObj.isPrimitive) { - buff.append("ptr< "); - } - } - buff.append(classObj.toString()); - if (refCount) { - if (!classObj.isPrimitive) { - buff.append(" >"); - } - } - for (int i = 0; i < arrayLevel; i++) { - if (refCount) { - buff.append(" >"); - } else { - if (!classObj.isPrimitive) { - buff.append("*"); - } - } - buff.append(" >"); - } - if (!refCount) { - if (isObject()) { - buff.append("*"); - } - } - return buff.toString(); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object other) { - if (other instanceof Type) { - Type t = (Type) other; - return t.classObj.equals(classObj) && t.arrayLevel == arrayLevel - && t.isVarArgs == isVarArgs; - } - return false; - } - - /** - * Get the default value, for primitive types (0 usually). - * - * @param context the context - * @return the expression - */ - public Expr getDefaultValue(JavaParser context) { - if (classObj.isPrimitive) { - LiteralExpr literal = new LiteralExpr(context, classObj.className); - literal.literal = "0"; - CastExpr cast = new CastExpr(); - cast.type = this; - cast.expr = literal; - cast.type = this; - return cast; - } - LiteralExpr literal = new LiteralExpr(context, classObj.className); - literal.literal = "null"; - return literal; - } - -} - diff --git a/h2/src/tools/org/h2/java/Expr.java b/h2/src/tools/org/h2/java/Expr.java deleted file mode 100644 index ed72d184bd..0000000000 --- a/h2/src/tools/org/h2/java/Expr.java +++ /dev/null @@ -1,736 +0,0 @@ -/* - * 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.java; - -import java.util.ArrayList; -import java.util.Iterator; - -/** - * An expression. - */ -public interface Expr { - - /** - * Get the C++ code. - * - * @return the C++ code - */ - String asString(); - - Type getType(); - void setType(Type type); - -} - -/** - * The base expression class. - */ -abstract class ExprBase implements Expr { - @Override - public final String toString() { - return "_" + asString() + "_"; - } -} - -/** - * A method call. - */ -class CallExpr extends ExprBase { - - /** - * The parameters. - */ - final ArrayList args = new ArrayList<>(); - - private final JavaParser context; - private final String className; - private final String name; - private Expr expr; - private ClassObj classObj; - private MethodObj method; - private Type type; - - CallExpr(JavaParser context, Expr expr, String className, String name) { - this.context = context; - this.expr = expr; - this.className = className; - this.name = name; - } - - private void initMethod() { - if (method != null) { - return; - } - if (className != null) { - classObj = context.getClassObj(className); - } else { - classObj = expr.getType().classObj; - } - method = classObj.getMethod(name, args); - if (method.isStatic) { - expr = null; - } - } - - @Override - public String asString() { - StringBuilder buff = new StringBuilder(); - initMethod(); - if (method.isIgnore) { - if (args.isEmpty()) { - // ignore - } else if (args.size() == 1) { - buff.append(args.get(0)); - } else { - throw new IllegalArgumentException( - "Cannot ignore method with multiple arguments: " + method); - } - } else { - if (expr == null) { - // static method - buff.append(JavaParser.toC(classObj.toString() + "." + method.name)); - } else { - buff.append(expr.asString()).append("->"); - buff.append(method.name); - } - buff.append("("); - int i = 0; - Iterator paramIt = method.parameters.values().iterator(); - for (Expr a : args) { - if (i > 0) { - buff.append(", "); - } - FieldObj f = paramIt.next(); - i++; - a.setType(f.type); - buff.append(a.asString()); - } - buff.append(")"); - } - return buff.toString(); - } - - @Override - public Type getType() { - initMethod(); - return method.returnType; - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * A assignment expression. - */ -class AssignExpr extends ExprBase { - - /** - * The target variable or field. - */ - Expr left; - - /** - * The operation (=, +=,...). - */ - String op; - - /** - * The expression. - */ - Expr right; - - /** - * The type. - */ - Type type; - - @Override - public String asString() { - right.setType(left.getType()); - return left.asString() + " " + op + " " + right.asString(); - } - - @Override - public Type getType() { - return left.getType(); - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * A conditional expression. - */ -class ConditionalExpr extends ExprBase { - - /** - * The condition. - */ - Expr condition; - - /** - * The 'true' expression. - */ - Expr ifTrue; - - /** - * The 'false' expression. - */ - Expr ifFalse; - - @Override - public String asString() { - return condition.asString() + " ? " + ifTrue.asString() + " : " - + ifFalse.asString(); - } - - @Override - public Type getType() { - return ifTrue.getType(); - } - - @Override - public void setType(Type type) { - ifTrue.setType(type); - ifFalse.setType(type); - } - -} - -/** - * A literal. - */ -class LiteralExpr extends ExprBase { - - /** - * The literal expression. - */ - String literal; - - private final JavaParser context; - private final String className; - private Type type; - - public LiteralExpr(JavaParser context, String className) { - this.context = context; - this.className = className; - } - - @Override - public String asString() { - if ("null".equals(literal)) { - Type t = getType(); - if (t.isObject()) { - return "(" + getType().asString() + ") 0"; - } - return t.asString() + "()"; - } - return literal; - } - - @Override - public Type getType() { - if (type == null) { - type = new Type(); - type.classObj = context.getClassObj(className); - } - return type; - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * An operation. - */ -class OpExpr extends ExprBase { - - /** - * The left hand side. - */ - Expr left; - - /** - * The operation. - */ - String op; - - /** - * The right hand side. - */ - Expr right; - - private final JavaParser context; - private Type type; - - OpExpr(JavaParser context) { - this.context = context; - } - - @Override - public String asString() { - if (left == null) { - return op + right.asString(); - } else if (right == null) { - return left.asString() + op; - } - if (op.equals(">>>")) { - // ujint / ujlong - return "(((u" + left.getType() + ") " + left + ") >> " + right + ")"; - } else if (op.equals("+")) { - if (left.getType().isObject() || right.getType().isObject()) { - // TODO convert primitive to String, call toString - StringBuilder buff = new StringBuilder(); - if (type.refCount) { - buff.append("ptr(new java_lang_StringBuilder("); - } else { - buff.append("(new java_lang_StringBuilder("); - } - buff.append(convertToString(left)); - buff.append("))->append("); - buff.append(convertToString(right)); - buff.append(")->toString()"); - return buff.toString(); - } - } - return "(" + left.asString() + " " + op + " " + right.asString() + ")"; - } - - private String convertToString(Expr e) { - Type t = e.getType(); - if (t.arrayLevel > 0) { - return e.toString() + "->toString()"; - } - if (t.classObj.isPrimitive) { - ClassObj wrapper = context.getWrapper(t.classObj); - return JavaParser.toC(wrapper + ".toString") + "(" + e.asString() + ")"; - } else if (e.getType().asString().equals("java_lang_String*")) { - return e.asString(); - } - return e.asString() + "->toString()"; - } - - private static boolean isComparison(String op) { - return op.equals("==") || op.equals(">") || op.equals("<") || - op.equals(">=") || op.equals("<=") || op.equals("!="); - } - - @Override - public Type getType() { - if (left == null) { - return right.getType(); - } - if (right == null) { - return left.getType(); - } - if (isComparison(op)) { - Type t = new Type(); - t.classObj = JavaParser.getBuiltInClass("boolean"); - return t; - } - if (op.equals("+")) { - if (left.getType().isObject() || right.getType().isObject()) { - Type t = new Type(); - t.classObj = context.getClassObj("java.lang.String"); - return t; - } - } - Type lt = left.getType(); - Type rt = right.getType(); - if (lt.classObj.primitiveType < rt.classObj.primitiveType) { - return rt; - } - return lt; - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * A "new" expression. - */ -class NewExpr extends ExprBase { - - /** - * The class. - */ - ClassObj classObj; - - /** - * The constructor parameters (for objects). - */ - final ArrayList args = new ArrayList<>(); - - /** - * The array bounds (for arrays). - */ - final ArrayList arrayInitExpr = new ArrayList<>(); - - /** - * The type. - */ - Type type; - - @Override - public String asString() { - boolean refCount = type.refCount; - StringBuilder buff = new StringBuilder(); - if (!arrayInitExpr.isEmpty()) { - if (refCount) { - if (classObj.isPrimitive) { - buff.append("ptr< array< " + classObj + " > >"); - } else { - buff.append("ptr< array< ptr< " + classObj + " > > >"); - } - } - if (classObj.isPrimitive) { - buff.append("(new array< " + classObj + " >(1 "); - } else { - if (refCount) { - buff.append("(new array< ptr< " + classObj + " > >(1 "); - } else { - buff.append("(new array< " + classObj + "* >(1 "); - } - } - for (Expr e : arrayInitExpr) { - buff.append("* ").append(e.asString()); - } - buff.append("))"); - } else { - if (refCount) { - buff.append("ptr< " + classObj + " >"); - } - buff.append("(new " + classObj); - buff.append("("); - int i = 0; - for (Expr a : args) { - if (i++ > 0) { - buff.append(", "); - } - buff.append(a.asString()); - } - buff.append("))"); - } - return buff.toString(); - } - - @Override - public Type getType() { - Type t = new Type(); - t.classObj = classObj; - t.arrayLevel = arrayInitExpr.size(); - return t; - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * A String literal. - */ -class StringExpr extends ExprBase { - - /** - * The constant name. - */ - String constantName; - - /** - * The literal. - */ - String text; - - private final JavaParser context; - private Type type; - - StringExpr(JavaParser context) { - this.context = context; - } - - @Override - public String asString() { - return constantName; - } - - @Override - public Type getType() { - if (type == null) { - type = new Type(); - type.classObj = context.getClassObj("java.lang.String"); - } - return type; - } - - /** - * Encode the String to Java syntax. - * - * @param s the string - * @return the encoded string - */ - static String javaEncode(String s) { - StringBuilder buff = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - switch (c) { - case '\t': - // HT horizontal tab - buff.append("\\t"); - break; - case '\n': - // LF linefeed - buff.append("\\n"); - break; - case '\f': - // FF form feed - buff.append("\\f"); - break; - case '\r': - // CR carriage return - buff.append("\\r"); - break; - case '"': - // double quote - buff.append("\\\""); - break; - case '\\': - // backslash - buff.append("\\\\"); - break; - default: - int ch = c & 0xffff; - if (ch >= ' ' && (ch < 0x80)) { - buff.append(c); - // not supported in properties files - // } else if(ch < 0xff) { - // buff.append("\\"); - // // make sure it's three characters (0x200 is octal 1000) - // buff.append(Integer.toOctalString(0x200 | ch).substring(1)); - } else { - buff.append("\\u"); - // make sure it's four characters - buff.append(Integer.toHexString(0x10000 | ch).substring(1)); - } - } - } - return buff.toString(); - } - - @Override - public void setType(Type type) { - // ignore - } - -} - -/** - * A variable. - */ -class VariableExpr extends ExprBase { - - /** - * The variable name. - */ - String name; - - /** - * The base expression (the first element in a.b variables). - */ - Expr base; - - /** - * The field. - */ - FieldObj field; - - private Type type; - private final JavaParser context; - - VariableExpr(JavaParser context) { - this.context = context; - } - - @Override - public String asString() { - init(); - StringBuilder buff = new StringBuilder(); - if (base != null) { - buff.append(base.asString()).append("->"); - } - if (field != null) { - if (field.isStatic) { - buff.append(JavaParser.toC(field.declaredClass + "." + field.name)); - } else if (field.name != null) { - buff.append(field.name); - } else if ("length".equals(name) && base.getType().arrayLevel > 0) { - buff.append("length()"); - } - } else { - buff.append(JavaParser.toC(name)); - } - return buff.toString(); - } - - private void init() { - if (field == null) { - Type t = base.getType(); - if (t.arrayLevel > 0) { - if ("length".equals(name)) { - field = new FieldObj(); - field.type = context.getClassObj("int").baseType; - } else { - throw new IllegalArgumentException("Unknown array method: " + name); - } - } else { - field = t.classObj.getField(name); - } - } - } - - @Override - public Type getType() { - init(); - return field.type; - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * An array initializer expression. - */ -class ArrayInitExpr extends ExprBase { - - /** - * The expression list. - */ - final ArrayList list = new ArrayList<>(); - - /** - * The type. - */ - Type type; - - @Override - public Type getType() { - return type; - } - - @Override - public String asString() { - StringBuilder buff = new StringBuilder("{ "); - int i = 0; - for (Expr e : list) { - if (i++ > 0) { - buff.append(", "); - } - buff.append(e.toString()); - } - buff.append(" }"); - return buff.toString(); - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * A type cast expression. - */ -class CastExpr extends ExprBase { - - /** - * The expression. - */ - Expr expr; - - /** - * The cast type. - */ - Type type; - - @Override - public Type getType() { - return type; - } - - @Override - public String asString() { - return "(" + type.asString() + ") " + expr.asString(); - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} - -/** - * An array access expression (get or set). - */ -class ArrayAccessExpr extends ExprBase { - - /** - * The base expression. - */ - Expr base; - - /** - * The index. - */ - Expr index; - - /** - * The type. - */ - Type type; - - @Override - public Type getType() { - Type t = new Type(); - t.classObj = base.getType().classObj; - t.arrayLevel = base.getType().arrayLevel - 1; - return t; - } - - @Override - public String asString() { - return base.asString() + "->at(" + index.asString() + ")"; - } - - @Override - public void setType(Type type) { - this.type = type; - } - -} diff --git a/h2/src/tools/org/h2/java/Ignore.java b/h2/src/tools/org/h2/java/Ignore.java deleted file mode 100644 index 1ed8d3708f..0000000000 --- a/h2/src/tools/org/h2/java/Ignore.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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.java; - -/** - * This annotation marks methods that are only needed for testing. - */ -public @interface Ignore { - // empty -} diff --git a/h2/src/tools/org/h2/java/JavaParser.java b/h2/src/tools/org/h2/java/JavaParser.java deleted file mode 100644 index 9eadb1ddae..0000000000 --- a/h2/src/tools/org/h2/java/JavaParser.java +++ /dev/null @@ -1,1848 +0,0 @@ -/* - * 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.java; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.RandomAccessFile; -import java.nio.charset.StandardCharsets; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; - -/** - * Converts Java to C. - */ -public class JavaParser { - - /** - * Whether ref-counting is used. - */ - public static final boolean REF_COUNT = false; - - /** - * Whether ref-counting is used for constants. - */ - public static final boolean REF_COUNT_STATIC = false; - - private static final HashMap BUILT_IN_CLASSES = new HashMap<>(); - - private static final int TOKEN_LITERAL_CHAR = 0; - private static final int TOKEN_LITERAL_STRING = 1; - private static final int TOKEN_LITERAL_NUMBER = 2; - private static final int TOKEN_RESERVED = 3; - private static final int TOKEN_IDENTIFIER = 4; - private static final int TOKEN_OTHER = 5; - - private static final HashSet RESERVED = new HashSet<>(); - private static final HashMap JAVA_IMPORT_MAP = new HashMap<>(); - - private final ArrayList allClasses = new ArrayList<>(); - - private String source; - - private ParseState current = new ParseState(); - - private String packageName; - private ClassObj classObj; - private int nextClassId; - private MethodObj method; - private FieldObj thisPointer; - private final HashMap importMap = new HashMap<>(); - private final HashMap classes = new HashMap<>(); - private final LinkedHashMap localVars = - new LinkedHashMap<>(); - private final HashMap allMethodsMap = new HashMap<>(); - private final ArrayList nativeHeaders = new ArrayList<>(); - private final HashMap stringToStringConstantMap = new HashMap<>(); - private final HashMap stringConstantToStringMap = new HashMap<>(); - - public JavaParser() { - addBuiltInTypes(); - } - - private void addBuiltInTypes() { - String[] list = { "abstract", "continue", "for", "new", "switch", - "assert", "default", "if", "package", "synchronized", - "boolean", "do", "goto", "private", "this", "break", "double", - "implements", "protected", "throw", "byte", "else", "import", - "public", "throws", "case", "enum", "instanceof", "return", - "transient", "catch", "extends", "int", "short", "try", "char", - "final", "interface", "static", "void", "class", "finally", - "long", "strictfp", "volatile", "const", "float", "native", - "super", "while", "true", "false", "null" }; - for (String s : list) { - RESERVED.add(s); - } - int id = 0; - addBuiltInType(id++, true, 0, "void"); - addBuiltInType(id++, true, 1, "boolean"); - addBuiltInType(id++, true, 2, "byte"); - addBuiltInType(id++, true, 3, "short"); - addBuiltInType(id++, true, 4, "char"); - addBuiltInType(id++, true, 5, "int"); - addBuiltInType(id++, true, 6, "long"); - addBuiltInType(id++, true, 7, "float"); - addBuiltInType(id++, true, 8, "double"); - String[] java = { "Boolean", "Byte", "Character", "Class", - "ClassLoader", "Double", "Float", "Integer", "Long", "Math", - "Number", "Object", "Runtime", "Short", "String", - "StringBuffer", "StringBuilder", "System", "Thread", - "ThreadGroup", "ThreadLocal", "Throwable", "Void" }; - for (String s : java) { - JAVA_IMPORT_MAP.put(s, "java.lang." + s); - addBuiltInType(id++, false, 0, "java.lang." + s); - } - nextClassId = id; - } - - /** - * Get the wrapper class for the given primitive class. - * - * @param c the class - * @return the wrapper class - */ - ClassObj getWrapper(ClassObj c) { - switch (c.id) { - case 1: - return getClass("java.lang.Boolean"); - case 2: - return getClass("java.lang.Byte"); - case 3: - return getClass("java.lang.Short"); - case 4: - return getClass("java.lang.Character"); - case 5: - return getClass("java.lang.Integer"); - case 6: - return getClass("java.lang.Long"); - case 7: - return getClass("java.lang.Float"); - case 8: - return getClass("java.lang.Double"); - } - throw new RuntimeException("not a primitive type: " + classObj); - } - - private void addBuiltInType(int id, boolean primitive, int primitiveType, - String type) { - ClassObj c = new ClassObj(); - c.id = id; - c.className = type; - c.isPrimitive = primitive; - c.primitiveType = primitiveType; - BUILT_IN_CLASSES.put(type, c); - addClass(c); - } - - private void addClass(ClassObj c) { - int id = c.id; - while (id >= allClasses.size()) { - allClasses.add(null); - } - allClasses.set(id, c); - } - - /** - * Parse the source code. - * - * @param baseDir the base directory - * @param className the fully qualified name of the class to parse - */ - void parse(String baseDir, String className) { - String fileName = baseDir + "/" + className.replace('.', '/') + ".java"; - current = new ParseState(); - try { - RandomAccessFile file = new RandomAccessFile(fileName, "r"); - byte[] buff = new byte[(int) file.length()]; - file.readFully(buff); - source = new String(buff, StandardCharsets.UTF_8); - file.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - source = replaceUnicode(source); - source = removeRemarks(source); - try { - readToken(); - parseCompilationUnit(); - } catch (Exception e) { - throw new RuntimeException(source.substring(0, current.index) - + "[*]" + source.substring(current.index), e); - } - } - - private static String cleanPackageName(String name) { - if (name.startsWith("org.h2.java.lang") - || name.startsWith("org.h2.java.io")) { - return name.substring("org.h2.".length()); - } - return name; - } - - private void parseCompilationUnit() { - if (readIf("package")) { - packageName = cleanPackageName(readQualifiedIdentifier()); - read(";"); - } - while (readIf("import")) { - String importPackageName = cleanPackageName(readQualifiedIdentifier()); - String importClass = importPackageName.substring(importPackageName - .lastIndexOf('.') + 1); - importMap.put(importClass, importPackageName); - read(";"); - } - while (true) { - Statement s = readNativeStatementIf(); - if (s == null) { - break; - } - nativeHeaders.add(s); - } - while (true) { - boolean isPublic = readIf("public"); - boolean isInterface; - if (readIf("class")) { - isInterface = false; - } else { - read("interface"); - isInterface = true; - } - String name = readIdentifier(); - classObj = BUILT_IN_CLASSES.get(packageName + "." + name); - if (classObj == null) { - classObj = new ClassObj(); - classObj.id = nextClassId++; - } - classObj.isPublic = isPublic; - classObj.isInterface = isInterface; - classObj.className = packageName == null ? "" : (packageName + ".") - + name; - // import this class - importMap.put(name, classObj.className); - addClass(classObj); - classes.put(classObj.className, classObj); - if (readIf("extends")) { - classObj.superClassName = readQualifiedIdentifier(); - } - if (readIf("implements")) { - while (true) { - classObj.interfaceNames.add(readQualifiedIdentifier()); - if (!readIf(",")) { - break; - } - } - } - parseClassBody(); - if (current.token == null) { - break; - } - } - } - - private boolean isTypeOrIdentifier() { - if (BUILT_IN_CLASSES.containsKey(current.token)) { - return true; - } - return current.type == TOKEN_IDENTIFIER; - } - - private ClassObj getClass(String type) { - ClassObj c = getClassIf(type); - if (c == null) { - throw new RuntimeException("Unknown type: " + type); - } - return c; - } - - /** - * Get the class for a built-in type. - * - * @param type the type - * @return the class or null if not found - */ - static ClassObj getBuiltInClass(String type) { - return BUILT_IN_CLASSES.get(type); - } - - private ClassObj getClassIf(String type) { - ClassObj c = BUILT_IN_CLASSES.get(type); - if (c != null) { - return c; - } - c = classes.get(type); - if (c != null) { - return c; - } - String mappedType = importMap.get(type); - if (mappedType == null) { - mappedType = JAVA_IMPORT_MAP.get(type); - if (mappedType == null) { - return null; - } - } - c = classes.get(mappedType); - if (c == null) { - c = BUILT_IN_CLASSES.get(mappedType); - if (c == null) { - throw new RuntimeException("Unknown class: " + mappedType); - } - } - return c; - } - - private void parseClassBody() { - read("{"); - localVars.clear(); - while (true) { - if (readIf("}")) { - break; - } - thisPointer = null; - while (true) { - Statement s = readNativeStatementIf(); - if (s == null) { - break; - } - classObj.nativeCode.add(s); - } - thisPointer = null; - HashSet annotations = new HashSet<>(); - while (readIf("@")) { - String annotation = readIdentifier(); - annotations.add(annotation); - } - boolean isIgnore = annotations.contains("Ignore"); - boolean isLocalField = annotations.contains("Local"); - boolean isStatic = false; - boolean isFinal = false; - boolean isPrivate = false; - boolean isPublic = false; - boolean isNative = false; - while (true) { - if (readIf("static")) { - isStatic = true; - } else if (readIf("final")) { - isFinal = true; - } else if (readIf("native")) { - isNative = true; - } else if (readIf("private")) { - isPrivate = true; - } else if (readIf("public")) { - isPublic = true; - } else { - break; - } - } - if (readIf("{")) { - method = new MethodObj(); - method.isIgnore = isIgnore; - method.name = isStatic ? "cl_init_obj" : ""; - method.isStatic = isStatic; - localVars.clear(); - if (!isStatic) { - initThisPointer(); - } - method.block = readStatement(); - classObj.addMethod(method); - } else { - String typeName = readTypeOrIdentifier(); - Type type = readType(typeName); - method = new MethodObj(); - method.isIgnore = isIgnore; - method.returnType = type; - method.isStatic = isStatic; - method.isFinal = isFinal; - method.isPublic = isPublic; - method.isPrivate = isPrivate; - method.isNative = isNative; - localVars.clear(); - if (!isStatic) { - initThisPointer(); - } - if (readIf("(")) { - if (type.classObj != classObj) { - throw getSyntaxException("Constructor of wrong type: " - + type); - } - method.name = ""; - method.isConstructor = true; - parseFormalParameters(method); - if (!readIf(";")) { - method.block = readStatement(); - } - classObj.addMethod(method); - addMethod(method); - } else { - String name = readIdentifier(); - if (name.endsWith("Method")) { - name = name.substring(0, - name.length() - "Method".length()); - } - method.name = name; - if (readIf("(")) { - parseFormalParameters(method); - if (!readIf(";")) { - method.block = readStatement(); - } - classObj.addMethod(method); - addMethod(method); - } else { - FieldObj field = new FieldObj(); - field.isIgnore = isIgnore; - field.isLocalField = isLocalField; - field.type = type; - field.name = name; - field.isStatic = isStatic; - field.isFinal = isFinal; - field.isPublic = isPublic; - field.isPrivate = isPrivate; - field.declaredClass = classObj; - if (readIf("=")) { - if (field.type.arrayLevel > 0 && readIf("{")) { - field.value = readArrayInit(field.type); - } else { - field.value = readExpr(); - } - } else { - field.value = field.type.getDefaultValue(this); - } - read(";"); - if (isStatic) { - classObj.addStaticField(field); - } else { - classObj.addInstanceField(field); - } - } - } - } - } - } - - private void addMethod(MethodObj m) { - if (m.isStatic) { - return; - } - MethodObj old = allMethodsMap.get(m.name); - if (old != null) { - old.isVirtual = true; - m.isVirtual = true; - } else { - allMethodsMap.put(m.name, m); - } - } - - private Expr readArrayInit(Type type) { - ArrayInitExpr expr = new ArrayInitExpr(); - expr.type = new Type(); - expr.type.classObj = type.classObj; - expr.type.arrayLevel = type.arrayLevel - 1; - if (!readIf("}")) { - while (true) { - expr.list.add(readExpr()); - if (readIf("}")) { - break; - } - read(","); - if (readIf("}")) { - break; - } - } - } - return expr; - } - - private void initThisPointer() { - thisPointer = new FieldObj(); - thisPointer.isVariable = true; - thisPointer.name = "this"; - thisPointer.type = new Type(); - thisPointer.type.classObj = classObj; - } - - private Type readType(String name) { - Type type = new Type(); - type.classObj = getClass(name); - while (readIf("[")) { - read("]"); - type.arrayLevel++; - } - if (readIf("...")) { - type.arrayLevel++; - type.isVarArgs = true; - } - return type; - } - - private void parseFormalParameters(MethodObj methodObj) { - if (readIf(")")) { - return; - } - while (true) { - FieldObj field = new FieldObj(); - field.isVariable = true; - String typeName = readTypeOrIdentifier(); - field.type = readType(typeName); - if (field.type.isVarArgs) { - methodObj.isVarArgs = true; - } - field.name = readIdentifier(); - methodObj.parameters.put(field.name, field); - if (readIf(")")) { - break; - } - read(","); - } - } - - private String readTypeOrIdentifier() { - if (current.type == TOKEN_RESERVED) { - if (BUILT_IN_CLASSES.containsKey(current.token)) { - return read(); - } - } - String s = readIdentifier(); - while (readIf(".")) { - s += "." + readIdentifier(); - } - return s; - } - - private Statement readNativeStatementIf() { - if (readIf("//")) { - boolean isC = readIdentifierIf("c"); - int start = current.index; - while (source.charAt(current.index) != '\n') { - current.index++; - } - String s = source.substring(start, current.index).trim(); - StatementNative stat = new StatementNative(s); - read(); - return isC ? stat : null; - } else if (readIf("/*")) { - boolean isC = readIdentifierIf("c"); - int start = current.index; - while (source.charAt(current.index) != '*' - || source.charAt(current.index + 1) != '/') { - current.index++; - } - String s = source.substring(start, current.index).trim(); - StatementNative stat = new StatementNative(s); - current.index += 2; - read(); - return isC ? stat : null; - } - return null; - } - - private Statement readStatement() { - Statement s = readNativeStatementIf(); - if (s != null) { - return s; - } - if (readIf(";")) { - return new EmptyStatement(); - } else if (readIf("{")) { - StatementBlock stat = new StatementBlock(); - while (true) { - if (readIf("}")) { - break; - } - stat.instructions.add(readStatement()); - } - return stat; - } else if (readIf("if")) { - IfStatement ifStat = new IfStatement(); - read("("); - ifStat.condition = readExpr(); - read(")"); - ifStat.block = readStatement(); - if (readIf("else")) { - ifStat.elseBlock = readStatement(); - } - return ifStat; - } else if (readIf("while")) { - WhileStatement whileStat = new WhileStatement(); - read("("); - whileStat.condition = readExpr(); - read(")"); - whileStat.block = readStatement(); - return whileStat; - } else if (readIf("break")) { - read(";"); - return new BreakStatement(); - } else if (readIf("continue")) { - read(";"); - return new ContinueStatement(); - } else if (readIf("switch")) { - - read("("); - SwitchStatement switchStat = new SwitchStatement(readExpr()); - read(")"); - read("{"); - while (true) { - if (readIf("default")) { - read(":"); - StatementBlock block = new StatementBlock(); - switchStat.setDefaultBlock(block); - while (true) { - block.instructions.add(readStatement()); - if (current.token.equals("case") - || current.token.equals("default") - || current.token.equals("}")) { - break; - } - } - } else if (readIf("case")) { - Expr expr = readExpr(); - read(":"); - StatementBlock block = new StatementBlock(); - while (true) { - block.instructions.add(readStatement()); - if (current.token.equals("case") - || current.token.equals("default") - || current.token.equals("}")) { - break; - } - } - switchStat.addCase(expr, block); - } else if (readIf("}")) { - break; - } - } - return switchStat; - } else if (readIf("for")) { - ForStatement forStat = new ForStatement(); - read("("); - ParseState back = copyParseState(); - try { - String typeName = readTypeOrIdentifier(); - Type type = readType(typeName); - String name = readIdentifier(); - FieldObj f = new FieldObj(); - f.name = name; - f.type = type; - f.isVariable = true; - localVars.put(name, f); - read(":"); - forStat.iterableType = type; - forStat.iterableVariable = name; - forStat.iterable = readExpr(); - } catch (Exception e) { - current = back; - forStat.init = readStatement(); - forStat.condition = readExpr(); - read(";"); - do { - forStat.updates.add(readExpr()); - } while (readIf(",")); - } - read(")"); - forStat.block = readStatement(); - return forStat; - } else if (readIf("do")) { - DoWhileStatement doWhileStat = new DoWhileStatement(); - doWhileStat.block = readStatement(); - read("while"); - read("("); - doWhileStat.condition = readExpr(); - read(")"); - read(";"); - return doWhileStat; - } else if (readIf("return")) { - ReturnStatement returnStat = new ReturnStatement(); - if (!readIf(";")) { - returnStat.expr = readExpr(); - read(";"); - } - return returnStat; - } else { - if (isTypeOrIdentifier()) { - ParseState start = copyParseState(); - String name = readTypeOrIdentifier(); - ClassObj c = getClassIf(name); - if (c != null) { - VarDecStatement dec = new VarDecStatement(); - dec.type = readType(name); - while (true) { - String varName = readIdentifier(); - Expr value = null; - if (readIf("=")) { - if (dec.type.arrayLevel > 0 && readIf("{")) { - value = readArrayInit(dec.type); - } else { - value = readExpr(); - } - } - FieldObj f = new FieldObj(); - f.isVariable = true; - f.type = dec.type; - f.name = varName; - localVars.put(varName, f); - dec.addVariable(varName, value); - if (readIf(";")) { - break; - } - read(","); - } - return dec; - } - current = start; - // ExprStatement - } - ExprStatement stat = new ExprStatement(readExpr()); - read(";"); - return stat; - } - } - - private ParseState copyParseState() { - ParseState state = new ParseState(); - state.index = current.index; - state.line = current.line; - state.token = current.token; - state.type = current.type; - return state; - } - - private Expr readExpr() { - Expr expr = readExpr1(); - String assign = current.token; - if (readIf("=") || readIf("+=") || readIf("-=") || readIf("*=") - || readIf("/=") || readIf("&=") || readIf("|=") || readIf("^=") - || readIf("%=") || readIf("<<=") || readIf(">>=") - || readIf(">>>=")) { - AssignExpr assignOp = new AssignExpr(); - assignOp.left = expr; - assignOp.op = assign; - assignOp.right = readExpr1(); - expr = assignOp; - } - return expr; - } - - private Expr readExpr1() { - Expr expr = readExpr2(); - if (readIf("?")) { - ConditionalExpr ce = new ConditionalExpr(); - ce.condition = expr; - ce.ifTrue = readExpr(); - read(":"); - ce.ifFalse = readExpr(); - return ce; - } - return expr; - } - - private Expr readExpr2() { - Expr expr = readExpr2a(); - while (true) { - String infixOp = current.token; - if (readIf("||")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2a(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2a() { - Expr expr = readExpr2b(); - while (true) { - String infixOp = current.token; - if (readIf("&&")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2b(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2b() { - Expr expr = readExpr2c(); - while (true) { - String infixOp = current.token; - if (readIf("|")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2c(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2c() { - Expr expr = readExpr2d(); - while (true) { - String infixOp = current.token; - if (readIf("^")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2d(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2d() { - Expr expr = readExpr2e(); - while (true) { - String infixOp = current.token; - if (readIf("&")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2e(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2e() { - Expr expr = readExpr2f(); - while (true) { - String infixOp = current.token; - if (readIf("==") || readIf("!=")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2f(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2f() { - Expr expr = readExpr2g(); - while (true) { - String infixOp = current.token; - if (readIf("<") || readIf(">") || readIf("<=") || readIf(">=")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2g(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2g() { - Expr expr = readExpr2h(); - while (true) { - String infixOp = current.token; - if (readIf("<<") || readIf(">>") || readIf(">>>")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2h(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2h() { - Expr expr = readExpr2i(); - while (true) { - String infixOp = current.token; - if (readIf("+") || readIf("-")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr2i(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr2i() { - Expr expr = readExpr3(); - while (true) { - String infixOp = current.token; - if (readIf("*") || readIf("/") || readIf("%")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = infixOp; - opExpr.right = readExpr3(); - expr = opExpr; - } else { - break; - } - } - return expr; - } - - private Expr readExpr3() { - if (readIf("(")) { - if (isTypeOrIdentifier()) { - ParseState start = copyParseState(); - String name = readTypeOrIdentifier(); - ClassObj c = getClassIf(name); - if (c != null) { - read(")"); - CastExpr expr = new CastExpr(); - expr.type = new Type(); - expr.type.classObj = c; - expr.expr = readExpr(); - return expr; - } - current = start; - } - Expr expr = readExpr(); - read(")"); - return expr; - } - String prefix = current.token; - if (readIf("++") || readIf("--") || readIf("!") || readIf("~") - || readIf("+") || readIf("-")) { - OpExpr expr = new OpExpr(this); - expr.op = prefix; - expr.right = readExpr3(); - return expr; - } - Expr expr = readExpr4(); - String suffix = current.token; - if (readIf("++") || readIf("--")) { - OpExpr opExpr = new OpExpr(this); - opExpr.left = expr; - opExpr.op = suffix; - expr = opExpr; - } - return expr; - } - - private Expr readExpr4() { - if (readIf("false")) { - LiteralExpr expr = new LiteralExpr(this, "boolean"); - expr.literal = "false"; - return expr; - } else if (readIf("true")) { - LiteralExpr expr = new LiteralExpr(this, "boolean"); - expr.literal = "true"; - return expr; - } else if (readIf("null")) { - LiteralExpr expr = new LiteralExpr(this, "java.lang.Object"); - expr.literal = "null"; - return expr; - } else if (current.type == TOKEN_LITERAL_NUMBER) { - // TODO or long, float, double - LiteralExpr expr = new LiteralExpr(this, "int"); - expr.literal = current.token.substring(1); - readToken(); - return expr; - } else if (current.type == TOKEN_LITERAL_CHAR) { - LiteralExpr expr = new LiteralExpr(this, "char"); - expr.literal = current.token + "'"; - readToken(); - return expr; - } else if (current.type == TOKEN_LITERAL_STRING) { - String text = current.token.substring(1); - StringExpr expr = getStringConstant(text); - readToken(); - return expr; - } - Expr expr; - expr = readExpr5(); - while (true) { - if (readIf(".")) { - String n = readIdentifier(); - if (readIf("(")) { - CallExpr e2 = new CallExpr(this, expr, null, n); - if (!readIf(")")) { - while (true) { - e2.args.add(readExpr()); - if (!readIf(",")) { - read(")"); - break; - } - } - } - expr = e2; - } else { - VariableExpr e2 = new VariableExpr(this); - e2.base = expr; - expr = e2; - e2.name = n; - } - } else if (readIf("[")) { - ArrayAccessExpr arrayExpr = new ArrayAccessExpr(); - arrayExpr.base = expr; - arrayExpr.index = readExpr(); - read("]"); - return arrayExpr; - } else { - break; - } - } - return expr; - } - - private StringExpr getStringConstant(String s) { - String c = stringToStringConstantMap.get(s); - if (c == null) { - StringBuilder buff = new StringBuilder(); - for (int i = 0; i < s.length() && i < 16; i++) { - char ch = s.charAt(i); - if (ch >= 'a' && ch <= 'z') { - // don't use Character.toUpperCase - // to avoid locale problems - // (the uppercase of 'i' is not always 'I') - buff.append((char) (ch + 'A' - 'a')); - } else if (ch >= 'A' && ch <= 'Z') { - buff.append(ch); - } else if (ch == '_' || ch == ' ') { - buff.append('_'); - } - } - c = buff.toString(); - if (c.length() == 0 || stringConstantToStringMap.containsKey(c)) { - if (c.length() == 0) { - c = "X"; - } - int i = 2; - for (;; i++) { - String c2 = c + "_" + i; - if (!stringConstantToStringMap.containsKey(c2)) { - c = c2; - break; - } - } - } - c = "STRING_" + c; - stringToStringConstantMap.put(s, c); - stringConstantToStringMap.put(c, s); - } - StringExpr expr = new StringExpr(this); - expr.text = s; - expr.constantName = c; - return expr; - } - - private Expr readExpr5() { - if (readIf("new")) { - NewExpr expr = new NewExpr(); - String typeName = readTypeOrIdentifier(); - expr.classObj = getClass(typeName); - if (readIf("(")) { - if (!readIf(")")) { - while (true) { - expr.args.add(readExpr()); - if (!readIf(",")) { - read(")"); - break; - } - } - } - } else { - while (readIf("[")) { - expr.arrayInitExpr.add(readExpr()); - read("]"); - } - } - return expr; - } - if (readIf("this")) { - VariableExpr expr = new VariableExpr(this); - if (thisPointer == null) { - throw getSyntaxException("'this' used in a static context"); - } - expr.field = thisPointer; - return expr; - } - String name = readIdentifier(); - if (readIf("(")) { - VariableExpr t; - if (thisPointer == null) { - // static method calling another static method - t = null; - } else { - // non-static method calling a static or non-static method - t = new VariableExpr(this); - t.field = thisPointer; - } - CallExpr expr = new CallExpr(this, t, classObj.className, name); - if (!readIf(")")) { - while (true) { - expr.args.add(readExpr()); - if (!readIf(",")) { - read(")"); - break; - } - } - } - return expr; - } - VariableExpr expr = new VariableExpr(this); - FieldObj f = localVars.get(name); - if (f == null) { - f = method.parameters.get(name); - } - if (f == null) { - f = classObj.staticFields.get(name); - } - if (f == null) { - f = classObj.instanceFields.get(name); - } - if (f == null) { - String imp = importMap.get(name); - if (imp == null) { - imp = JAVA_IMPORT_MAP.get(name); - } - if (imp != null) { - name = imp; - if (readIf(".")) { - String n = readIdentifier(); - if (readIf("(")) { - CallExpr e2 = new CallExpr(this, null, imp, n); - if (!readIf(")")) { - while (true) { - e2.args.add(readExpr()); - if (!readIf(",")) { - read(")"); - break; - } - } - } - return e2; - } - VariableExpr e2 = new VariableExpr(this); - // static member variable - e2.name = imp + "." + n; - ClassObj c = classes.get(imp); - FieldObj sf = c.staticFields.get(n); - e2.field = sf; - return e2; - } - // TODO static field or method of a class - } - } - expr.field = f; - if (f != null && (!f.isVariable && !f.isStatic)) { - VariableExpr ve = new VariableExpr(this); - ve.field = thisPointer; - expr.base = ve; - if (thisPointer == null) { - throw getSyntaxException("'this' used in a static context"); - } - } - expr.name = name; - return expr; - } - - private void read(String string) { - if (!readIf(string)) { - throw getSyntaxException(string + " expected, got " + current.token); - } - } - - private String readQualifiedIdentifier() { - String id = readIdentifier(); - if (localVars.containsKey(id)) { - return id; - } - if (classObj != null) { - if (classObj.staticFields.containsKey(id)) { - return id; - } - if (classObj.instanceFields.containsKey(id)) { - return id; - } - } - String fullName = importMap.get(id); - if (fullName != null) { - return fullName; - } - while (readIf(".")) { - id += "." + readIdentifier(); - } - return id; - } - - private String readIdentifier() { - if (current.type != TOKEN_IDENTIFIER) { - throw getSyntaxException("identifier expected, got " - + current.token); - } - String result = current.token; - readToken(); - return result; - } - - private boolean readIdentifierIf(String token) { - if (current.type == TOKEN_IDENTIFIER && token.equals(current.token)) { - readToken(); - return true; - } - return false; - } - - private boolean readIf(String token) { - if (current.type != TOKEN_IDENTIFIER && token.equals(current.token)) { - readToken(); - return true; - } - return false; - } - - private String read() { - String token = current.token; - readToken(); - return token; - } - - private RuntimeException getSyntaxException(String message) { - return new RuntimeException(message, new ParseException(source, - current.index)); - } - - /** - * Replace all Unicode escapes. - * - * @param s the text - * @return the cleaned text - */ - static String replaceUnicode(String s) { - if (s.indexOf("\\u") < 0) { - return s; - } - StringBuilder buff = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - if (s.substring(i).startsWith("\\\\")) { - buff.append("\\\\"); - i++; - } else if (s.substring(i).startsWith("\\u")) { - i += 2; - while (s.charAt(i) == 'u') { - i++; - } - String c = s.substring(i, i + 4); - buff.append((char) Integer.parseInt(c, 16)); - i += 4; - } else { - buff.append(s.charAt(i)); - } - } - return buff.toString(); - } - - /** - * Replace all Unicode escapes and remove all remarks. - * - * @param s the source code - * @return the cleaned source code - */ - static String removeRemarks(String s) { - char[] chars = s.toCharArray(); - for (int i = 0; i >= 0 && i < s.length(); i++) { - if (s.charAt(i) == '\'') { - i++; - while (true) { - if (s.charAt(i) == '\\') { - i++; - } else if (s.charAt(i) == '\'') { - break; - } - i++; - } - continue; - } else if (s.charAt(i) == '\"') { - i++; - while (true) { - if (s.charAt(i) == '\\') { - i++; - } else if (s.charAt(i) == '\"') { - break; - } - i++; - } - continue; - } - String sub = s.substring(i); - if (sub.startsWith("/*") && !sub.startsWith("/* c:")) { - int j = i; - i = s.indexOf("*/", i + 2) + 2; - for (; j < i; j++) { - if (chars[j] > ' ') { - chars[j] = ' '; - } - } - } else if (sub.startsWith("//") && !sub.startsWith("// c:")) { - int j = i; - i = s.indexOf('\n', i); - while (j < i) { - chars[j++] = ' '; - } - } - } - return new String(chars) + " "; - } - - private void readToken() { - int ch; - while (true) { - if (current.index >= source.length()) { - current.token = null; - return; - } - ch = source.charAt(current.index); - if (ch == '\n') { - current.line++; - } else if (ch > ' ') { - break; - } - current.index++; - } - int start = current.index; - if (Character.isJavaIdentifierStart(ch)) { - while (Character.isJavaIdentifierPart(source.charAt(current.index))) { - current.index++; - } - current.token = source.substring(start, current.index); - if (RESERVED.contains(current.token)) { - current.type = TOKEN_RESERVED; - } else { - current.type = TOKEN_IDENTIFIER; - } - return; - } else if (Character.isDigit(ch) - || (ch == '.' && Character.isDigit(source - .charAt(current.index + 1)))) { - String s = source.substring(current.index); - current.token = "0" + readNumber(s); - current.index += current.token.length() - 1; - current.type = TOKEN_LITERAL_NUMBER; - return; - } - current.index++; - switch (ch) { - case '\'': { - while (true) { - if (source.charAt(current.index) == '\\') { - current.index++; - } else if (source.charAt(current.index) == '\'') { - break; - } - current.index++; - } - current.index++; - current.token = source.substring(start + 1, current.index); - current.token = "\'" + javaDecode(current.token, '\''); - current.type = TOKEN_LITERAL_CHAR; - return; - } - case '\"': { - while (true) { - if (source.charAt(current.index) == '\\') { - current.index++; - } else if (source.charAt(current.index) == '\"') { - break; - } - current.index++; - } - current.index++; - current.token = source.substring(start + 1, current.index); - current.token = "\"" + javaDecode(current.token, '\"'); - current.type = TOKEN_LITERAL_STRING; - return; - } - case '(': - case ')': - case '[': - case ']': - case '{': - case '}': - case ';': - case ',': - case '?': - case ':': - case '@': - break; - case '.': - if (source.charAt(current.index) == '.' - && source.charAt(current.index + 1) == '.') { - current.index += 2; - } - break; - case '+': - if (source.charAt(current.index) == '=' - || source.charAt(current.index) == '+') { - current.index++; - } - break; - case '-': - if (source.charAt(current.index) == '=' - || source.charAt(current.index) == '-') { - current.index++; - } - break; - case '>': - if (source.charAt(current.index) == '>') { - current.index++; - if (source.charAt(current.index) == '>') { - current.index++; - } - } - if (source.charAt(current.index) == '=') { - current.index++; - } - break; - case '<': - if (source.charAt(current.index) == '<') { - current.index++; - } - if (source.charAt(current.index) == '=') { - current.index++; - } - break; - case '/': - if (source.charAt(current.index) == '*' - || source.charAt(current.index) == '/' - || source.charAt(current.index) == '=') { - current.index++; - } - break; - case '*': - case '~': - case '!': - case '=': - case '%': - case '^': - if (source.charAt(current.index) == '=') { - current.index++; - } - break; - case '&': - if (source.charAt(current.index) == '&') { - current.index++; - } else if (source.charAt(current.index) == '=') { - current.index++; - } - break; - case '|': - if (source.charAt(current.index) == '|') { - current.index++; - } else if (source.charAt(current.index) == '=') { - current.index++; - } - break; - } - current.type = TOKEN_OTHER; - current.token = source.substring(start, current.index); - } - - /** - * Parse a number literal and returns it. - * - * @param s the source code - * @return the number - */ - static String readNumber(String s) { - int i = 0; - if (s.startsWith("0x") || s.startsWith("0X")) { - i = 2; - while (true) { - char ch = s.charAt(i); - if ((ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') - && (ch < 'A' || ch > 'F')) { - break; - } - i++; - } - if (s.charAt(i) == 'l' || s.charAt(i) == 'L') { - i++; - } - } else { - while (true) { - char ch = s.charAt(i); - if ((ch < '0' || ch > '9') && ch != '.') { - break; - } - i++; - } - if (s.charAt(i) == 'e' || s.charAt(i) == 'E') { - i++; - if (s.charAt(i) == '-' || s.charAt(i) == '+') { - i++; - } - while (Character.isDigit(s.charAt(i))) { - i++; - } - } - if (s.charAt(i) == 'f' || s.charAt(i) == 'F' || s.charAt(i) == 'd' - || s.charAt(i) == 'D' || s.charAt(i) == 'L' - || s.charAt(i) == 'l') { - i++; - } - } - return s.substring(0, i); - } - - private static RuntimeException getFormatException(String s, int i) { - return new RuntimeException(new ParseException(s, i)); - } - - private static String javaDecode(String s, char end) { - StringBuilder buff = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c == end) { - break; - } else if (c == '\\') { - if (i >= s.length()) { - throw getFormatException(s, s.length() - 1); - } - c = s.charAt(++i); - switch (c) { - case 't': - buff.append('\t'); - break; - case 'r': - buff.append('\r'); - break; - case 'n': - buff.append('\n'); - break; - case 'b': - buff.append('\b'); - break; - case 'f': - buff.append('\f'); - break; - case '"': - buff.append('"'); - break; - case '\'': - buff.append('\''); - break; - case '\\': - buff.append('\\'); - break; - case 'u': { - try { - c = (char) (Integer.parseInt(s.substring(i + 1, i + 5), - 16)); - } catch (NumberFormatException e) { - throw getFormatException(s, i); - } - i += 4; - buff.append(c); - break; - } - default: - if (c >= '0' && c <= '9') { - try { - c = (char) (Integer.parseInt(s.substring(i, i + 3), - 8)); - } catch (NumberFormatException e) { - throw getFormatException(s, i); - } - i += 2; - buff.append(c); - } else { - throw getFormatException(s, i); - } - } - } else { - buff.append(c); - } - } - return buff.toString(); - } - - /** - * Write the C++ header. - * - * @param out the output writer - */ - void writeHeader(PrintWriter out) { - for (Statement s : nativeHeaders) { - out.println(s.asString()); - } - if (JavaParser.REF_COUNT_STATIC) { - out.println("#define STRING(s) STRING_REF(s)"); - } else { - out.println("#define STRING(s) STRING_PTR(s)"); - } - out.println(); - for (ClassObj c : classes.values()) { - out.println("class " + toC(c.className) + ";"); - } - for (ClassObj c : classes.values()) { - for (FieldObj f : c.staticFields.values()) { - StringBuilder buff = new StringBuilder(); - buff.append("extern "); - if (f.isFinal) { - buff.append("const "); - } - buff.append(f.type.asString()); - buff.append(" ").append(toC(c.className + "." + f.name)); - buff.append(";"); - out.println(buff.toString()); - } - for (ArrayList list : c.methods.values()) { - for (MethodObj m : list) { - if (m.isIgnore) { - continue; - } - if (m.isStatic) { - out.print(m.returnType.asString()); - out.print(" " + toC(c.className + "_" + m.name) + "("); - int i = 0; - for (FieldObj p : m.parameters.values()) { - if (i > 0) { - out.print(", "); - } - out.print(p.type.asString() + " " + p.name); - i++; - } - out.println(");"); - } - } - } - out.print("class " + toC(c.className) + " : public "); - if (c.superClassName == null) { - if (c.className.equals("java.lang.Object")) { - out.print("RefBase"); - } else { - out.print("java_lang_Object"); - } - } else { - out.print(toC(c.superClassName)); - } - out.println(" {"); - out.println("public:"); - for (FieldObj f : c.instanceFields.values()) { - out.print(" "); - out.print(f.type.asString() + " " + f.name); - out.println(";"); - } - out.println("public:"); - for (ArrayList list : c.methods.values()) { - for (MethodObj m : list) { - if (m.isIgnore) { - continue; - } - if (m.isStatic) { - continue; - } - if (m.isConstructor) { - out.print(" " + toC(c.className) + "("); - } else { - out.print(" " + m.returnType.asString() + " " - + m.name + "("); - } - int i = 0; - for (FieldObj p : m.parameters.values()) { - if (i > 0) { - out.print(", "); - } - out.print(p.type.asString()); - out.print(" " + p.name); - i++; - } - out.println(");"); - } - } - out.println("};"); - } - ArrayList constantNames = new ArrayList<>(stringConstantToStringMap.keySet()); - Collections.sort(constantNames); - for (String c : constantNames) { - String s = stringConstantToStringMap.get(c); - if (JavaParser.REF_COUNT_STATIC) { - out.println("ptr " + c + " = STRING(L\"" + s - + "\");"); - } else { - out.println("java_lang_String* " + c + " = STRING(L\"" + s - + "\");"); - } - } - } - - /** - * Write the C++ source code. - * - * @param out the output writer - */ - void writeSource(PrintWriter out) { - for (ClassObj c : classes.values()) { - out.println("/* " + c.className + " */"); - for (Statement s : c.nativeCode) { - out.println(s.asString()); - } - for (FieldObj f : c.staticFields.values()) { - StringBuilder buff = new StringBuilder(); - if (f.isFinal) { - buff.append("const "); - } - buff.append(f.type.asString()); - buff.append(" ").append(toC(c.className + "." + f.name)); - if (f.value != null) { - buff.append(" = ").append(f.value.asString()); - } - buff.append(";"); - out.println(buff.toString()); - } - for (ArrayList list : c.methods.values()) { - for (MethodObj m : list) { - if (m.isIgnore) { - continue; - } - if (m.isStatic) { - out.print(m.returnType.asString() + " " - + toC(c.className + "_" + m.name) + "("); - } else if (m.isConstructor) { - out.print(toC(c.className) + "::" + toC(c.className) - + "("); - } else { - out.print(m.returnType.asString() + " " - + toC(c.className) + "::" + m.name + "("); - } - int i = 0; - for (FieldObj p : m.parameters.values()) { - if (i > 0) { - out.print(", "); - } - out.print(p.type.asString() + " " + p.name); - i++; - } - out.println(") {"); - if (m.isConstructor) { - for (FieldObj f : c.instanceFields.values()) { - out.print(" "); - out.print("this->" + f.name); - out.print(" = " + f.value.asString()); - out.println(";"); - } - } - if (m.block != null) { - m.block.setMethod(m); - out.print(m.block.asString()); - } - out.println("}"); - out.println(); - } - } - } - } - - private static String indent(String s, int spaces) { - StringBuilder buff = new StringBuilder(s.length() + spaces); - for (int i = 0; i < s.length();) { - for (int j = 0; j < spaces; j++) { - buff.append(' '); - } - int n = s.indexOf('\n', i); - n = n < 0 ? s.length() : n + 1; - buff.append(s.substring(i, n)); - i = n; - } - if (!s.endsWith("\n")) { - buff.append('\n'); - } - return buff.toString(); - } - - /** - * Move the source code 4 levels to the right. - * - * @param o the source code - * @return the indented code - */ - static String indent(String o) { - return indent(o, 4); - } - - /** - * Get the C++ representation of this identifier. - * - * @param identifier the identifier - * @return the C representation - */ - static String toC(String identifier) { - return identifier.replace('.', '_'); - } - - ClassObj getClassObj() { - return classObj; - } - - /** - * Get the class of the given name. - * - * @param className the name - * @return the class - */ - ClassObj getClassObj(String className) { - ClassObj c = BUILT_IN_CLASSES.get(className); - if (c == null) { - c = classes.get(className); - } - return c; - } - -} - -/** - * The parse state. - */ -class ParseState { - - /** - * The parse index. - */ - int index; - - /** - * The token type - */ - int type; - - /** - * The token text. - */ - String token; - - /** - * The line number. - */ - int line; -} \ No newline at end of file diff --git a/h2/src/tools/org/h2/java/Local.java b/h2/src/tools/org/h2/java/Local.java deleted file mode 100644 index 2df19d9527..0000000000 --- a/h2/src/tools/org/h2/java/Local.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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.java; - -/** - * This annotation marks fields that are not shared and therefore don't need to - * be garbage collected separately. - */ -public @interface Local { - // empty -} diff --git a/h2/src/tools/org/h2/java/Statement.java b/h2/src/tools/org/h2/java/Statement.java deleted file mode 100644 index 13a5b2e8bf..0000000000 --- a/h2/src/tools/org/h2/java/Statement.java +++ /dev/null @@ -1,504 +0,0 @@ -/* - * 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.java; - -import java.util.ArrayList; - -/** - * A statement. - */ -public interface Statement { - - void setMethod(MethodObj method); - boolean isEnd(); - - /** - * Get the C++ code. - * - * @return the C++ code - */ - String asString(); - -} - -/** - * The base class for statements. - */ -abstract class StatementBase implements Statement { - - @Override - public boolean isEnd() { - return false; - } - -} - -/** - * A "return" statement. - */ -class ReturnStatement extends StatementBase { - - /** - * The return expression. - */ - Expr expr; - - private MethodObj method; - - @Override - public void setMethod(MethodObj method) { - this.method = method; - } - - @Override - public String asString() { - if (expr == null) { - return "return;"; - } - Type returnType = method.returnType; - expr.setType(returnType); - if (!expr.getType().isObject()) { - return "return " + expr.asString() + ";"; - } - if (returnType.refCount) { - return "return " + expr.getType().asString() + "(" + expr.asString() + ");"; - } - return "return " + expr.asString() + ";"; - } - -} - -/** - * A "do .. while" statement. - */ -class DoWhileStatement extends StatementBase { - - /** - * The condition. - */ - Expr condition; - - /** - * The execution block. - */ - Statement block; - - @Override - public void setMethod(MethodObj method) { - block.setMethod(method); - } - - @Override - public String asString() { - return "do {\n" + block + "} while (" + condition.asString() + ");"; - } - -} - -/** - * A "continue" statement. - */ -class ContinueStatement extends StatementBase { - - @Override - public void setMethod(MethodObj method) { - // ignore - } - - @Override - public String asString() { - return "continue;"; - } - -} - -/** - * A "break" statement. - */ -class BreakStatement extends StatementBase { - - @Override - public void setMethod(MethodObj method) { - // ignore - } - - @Override - public String asString() { - return "break;"; - } - -} - -/** - * An empty statement. - */ -class EmptyStatement extends StatementBase { - - @Override - public void setMethod(MethodObj method) { - // ignore - } - - @Override - public String asString() { - return ";"; - } - -} - -/** - * A "switch" statement. - */ -class SwitchStatement extends StatementBase { - - private StatementBlock defaultBlock; - private final ArrayList cases = new ArrayList<>(); - private final ArrayList blocks = - new ArrayList<>(); - private final Expr expr; - - public SwitchStatement(Expr expr) { - this.expr = expr; - } - - @Override - public void setMethod(MethodObj method) { - defaultBlock.setMethod(method); - for (StatementBlock b : blocks) { - b.setMethod(method); - } - } - - @Override - public String asString() { - StringBuilder buff = new StringBuilder(); - buff.append("switch (").append(expr.asString()).append(") {\n"); - for (int i = 0; i < cases.size(); i++) { - buff.append("case " + cases.get(i).asString() + ":\n"); - buff.append(blocks.get(i).toString()); - } - if (defaultBlock != null) { - buff.append("default:\n"); - buff.append(defaultBlock.toString()); - } - buff.append("}"); - return buff.toString(); - } - - public void setDefaultBlock(StatementBlock block) { - this.defaultBlock = block; - } - - /** - * Add a case. - * - * @param expr the case expression - * @param block the execution block - */ - public void addCase(Expr expr, StatementBlock block) { - cases.add(expr); - blocks.add(block); - } - -} - -/** - * An expression statement. - */ -class ExprStatement extends StatementBase { - - private final Expr expr; - - public ExprStatement(Expr expr) { - this.expr = expr; - } - - @Override - public void setMethod(MethodObj method) { - // ignore - } - - @Override - public String asString() { - return expr.asString() + ";"; - } - -} - -/** - * A "while" statement. - */ -class WhileStatement extends StatementBase { - - /** - * The condition. - */ - Expr condition; - - /** - * The execution block. - */ - Statement block; - - @Override - public void setMethod(MethodObj method) { - block.setMethod(method); - } - - @Override - public String asString() { - String w = "while (" + condition.asString() + ")"; - String s = block.toString(); - return w + "\n" + s; - } - -} - -/** - * An "if" statement. - */ -class IfStatement extends StatementBase { - - /** - * The condition. - */ - Expr condition; - - /** - * The execution block. - */ - Statement block; - - /** - * The else block. - */ - Statement elseBlock; - - @Override - public void setMethod(MethodObj method) { - block.setMethod(method); - if (elseBlock != null) { - elseBlock.setMethod(method); - } - } - - @Override - public String asString() { - String w = "if (" + condition.asString() + ") {\n"; - String s = block.asString(); - if (elseBlock != null) { - s += "} else {\n" + elseBlock.asString(); - } - return w + s + "}"; - } - -} - -/** - * A "for" statement. - */ -class ForStatement extends StatementBase { - - /** - * The init block. - */ - Statement init; - - /** - * The condition. - */ - Expr condition; - - /** - * The main loop block. - */ - Statement block; - - /** - * The update list. - */ - ArrayList updates = new ArrayList<>(); - - /** - * The type of the iterable. - */ - Type iterableType; - - /** - * The iterable variable name. - */ - String iterableVariable; - - /** - * The iterable expression. - */ - Expr iterable; - - @Override - public void setMethod(MethodObj method) { - block.setMethod(method); - } - - @Override - public String asString() { - StringBuilder buff = new StringBuilder(); - buff.append("for ("); - if (iterableType != null) { - Type it = iterable.getType(); - if (it != null && it.arrayLevel > 0) { - String idx = "i_" + iterableVariable; - buff.append("int " + idx + " = 0; " + - idx + " < " + iterable.asString() + "->length(); " + - idx + "++"); - buff.append(") {\n"); - buff.append(JavaParser.indent(iterableType + - " " + iterableVariable + " = " + - iterable.asString() + "->at("+ idx +");\n")); - buff.append(block.toString()).append("}"); - } else { - // TODO iterate over a collection - buff.append(iterableType).append(' '); - buff.append(iterableVariable).append(": "); - buff.append(iterable); - buff.append(") {\n"); - buff.append(block.toString()).append("}"); - } - } else { - buff.append(init.asString()); - buff.append(" ").append(condition.asString()).append("; "); - for (int i = 0; i < updates.size(); i++) { - if (i > 0) { - buff.append(", "); - } - buff.append(updates.get(i).asString()); - } - buff.append(") {\n"); - buff.append(block.asString()).append("}"); - } - return buff.toString(); - } - -} - -/** - * A statement block. - */ -class StatementBlock extends StatementBase { - - /** - * The list of instructions. - */ - final ArrayList instructions = new ArrayList<>(); - - @Override - public void setMethod(MethodObj method) { - for (Statement s : instructions) { - s.setMethod(method); - } - } - - @Override - public String asString() { - StringBuilder buff = new StringBuilder(); - for (Statement s : instructions) { - if (s.isEnd()) { - break; - } - buff.append(JavaParser.indent(s.asString())); - } - return buff.toString(); - } - -} - -/** - * A variable declaration. - */ -class VarDecStatement extends StatementBase { - - /** - * The type. - */ - Type type; - - private final ArrayList variables = new ArrayList<>(); - private final ArrayList values = new ArrayList<>(); - - @Override - public void setMethod(MethodObj method) { - // ignore - } - - @Override - public String asString() { - StringBuilder buff = new StringBuilder(); - buff.append(type.asString()).append(' '); - StringBuilder assign = new StringBuilder(); - for (int i = 0; i < variables.size(); i++) { - if (i > 0) { - buff.append(", "); - } - String varName = variables.get(i); - buff.append(varName); - Expr value = values.get(i); - if (value != null) { - if (!value.getType().isObject()) { - buff.append(" = ").append(value.asString()); - } else { - value.setType(type); - assign.append(varName).append(" = ").append(value.asString()).append(";\n"); - } - } - } - buff.append(";"); - if (assign.length() > 0) { - buff.append("\n"); - buff.append(assign); - } - return buff.toString(); - } - - /** - * Add a variable. - * - * @param name the variable name - * @param value the init value - */ - public void addVariable(String name, Expr value) { - variables.add(name); - values.add(value); - } - -} - -/** - * A native statement. - */ -class StatementNative extends StatementBase { - - private final String code; - - StatementNative(String code) { - this.code = code; - } - - @Override - public void setMethod(MethodObj method) { - // ignore - } - - @Override - public String asString() { - return code; - } - - @Override - public boolean isEnd() { - return code.equals("return;"); - } - -} - diff --git a/h2/src/tools/org/h2/java/Test.java b/h2/src/tools/org/h2/java/Test.java deleted file mode 100644 index 9ce40aece4..0000000000 --- a/h2/src/tools/org/h2/java/Test.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.java; - -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import org.h2.test.TestBase; - -/** - * A test for the Java parser. - */ -public class Test extends TestBase { - - /** - * Start the task with the given arguments. - * - * @param args the arguments, or null - */ - public static void main(String... args) throws IOException { - new Test().test(); - } - - @Override - public void test() throws IOException { - // g++ -o test test.cpp - // chmod +x test - // ./test - - // TODO initialize fields - - // include files: - // /usr/include/c++/4.2.1/tr1/stdio.h - // /usr/include/stdio.h - // inttypes.h - - // not supported yet: - // exceptions - // HexadecimalFloatingPointLiteral - // int x()[] { return null; } - // import static - // import * - // initializer blocks - // access to static fields with instance variable - // final variables (within blocks, parameter list) - // Identifier : (labels) - // ClassOrInterfaceDeclaration within blocks - // (or any other nested classes) - // assert - - assertEquals("\\\\" + "u0000", JavaParser.replaceUnicode("\\\\" + "u0000")); - assertEquals("\u0000", JavaParser.replaceUnicode("\\" + "u0000")); - assertEquals("\u0000", JavaParser.replaceUnicode("\\" + "uu0000")); - assertEquals("\\\\" + "\u0000", JavaParser.replaceUnicode("\\\\\\" + "u0000")); - - assertEquals("0", JavaParser.readNumber("0a")); - assertEquals("0l", JavaParser.readNumber("0l")); - assertEquals("0xFFL", JavaParser.readNumber("0xFFLx")); - assertEquals("0xDadaCafe", JavaParser.readNumber("0xDadaCafex")); - assertEquals("1.40e-45f", JavaParser.readNumber("1.40e-45fx")); - assertEquals("1e1f", JavaParser.readNumber("1e1fx")); - assertEquals("2.f", JavaParser.readNumber("2.fx")); - assertEquals(".3d", JavaParser.readNumber(".3dx")); - assertEquals("6.022137e+23f", JavaParser.readNumber("6.022137e+23f+1")); - - JavaParser parser = new JavaParser(); - parser.parse("src/tools/org/h2", "java.lang.Object"); - parser.parse("src/tools/org/h2", "java.lang.String"); - parser.parse("src/tools/org/h2", "java.lang.Math"); - parser.parse("src/tools/org/h2", "java.lang.Integer"); - parser.parse("src/tools/org/h2", "java.lang.Long"); - parser.parse("src/tools/org/h2", "java.lang.StringBuilder"); - parser.parse("src/tools/org/h2", "java.io.PrintStream"); - parser.parse("src/tools/org/h2", "java.lang.System"); - parser.parse("src/tools/org/h2", "java.util.Arrays"); - parser.parse("src/tools", "org.h2.java.TestApp"); - - PrintWriter w = new PrintWriter(System.out); - parser.writeHeader(w); - parser.writeSource(w); - w.flush(); - w = new PrintWriter(new FileWriter("bin/test.cpp")); - parser.writeHeader(w); - parser.writeSource(w); - w.close(); - - } - -} diff --git a/h2/src/tools/org/h2/java/TestApp.java b/h2/src/tools/org/h2/java/TestApp.java deleted file mode 100644 index cd848c6869..0000000000 --- a/h2/src/tools/org/h2/java/TestApp.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.java; - -/** - * A test application. - */ -public class TestApp { - -/* c: - -int main(int argc, char** argv) { -// org_h2_java_TestApp_main(0); - org_h2_java_TestApp_main(ptr > >()); -} - -*/ - - /** - * Run this application. - * - * @param args the command line arguments - */ - public static void main(String... args) { - String[] list = new String[1000]; - for (int i = 0; i < 1000; i++) { - list[i] = "Hello " + i; - } - - // time:29244000 mac g++ -O3 without array bound checks - // time:30673000 mac java - // time:32449000 mac g++ -O3 - // time:69692000 mac g++ -O3 ref counted - // time:1200000000 raspberry g++ -O3 - // time:1720000000 raspberry g++ -O3 ref counted - // time:1980469000 raspberry java IcedTea6 1.8.13 Cacao VM - // time:12962645810 raspberry java IcedTea6 1.8.13 Zero VM - // java -XXaltjvm=cacao - - for (int k = 0; k < 4; k++) { - long t = System.nanoTime(); - long h = 0; - for (int j = 0; j < 10000; j++) { - for (int i = 0; i < 1000; i++) { - String s = list[i]; - h = (h * 7) ^ s.hashCode(); - } - } - System.out.println("hash: " + h); - t = System.nanoTime() - t; - System.out.println("time:" + t); - } - } - -} diff --git a/h2/src/tools/org/h2/java/io/PrintStream.java b/h2/src/tools/org/h2/java/io/PrintStream.java deleted file mode 100644 index 4eed18ddb9..0000000000 --- a/h2/src/tools/org/h2/java/io/PrintStream.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.java.io; - -/** - * A print stream. - */ -public class PrintStream { - - /** - * Print the given string. - * - * @param s the string - */ - @SuppressWarnings("unused") - public void println(String s) { - // c: int x = s->chars->length(); - // c: printf("%.*S\n", x, s->chars->getPointer()); - } - -} diff --git a/h2/src/tools/org/h2/java/io/package.html b/h2/src/tools/org/h2/java/io/package.html deleted file mode 100644 index fb9167e95f..0000000000 --- a/h2/src/tools/org/h2/java/io/package.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - -Javadoc package documentation -

- -A simple implementation of the java.lang.* package for the Java parser. - -

\ No newline at end of file diff --git a/h2/src/tools/org/h2/java/lang/Integer.java b/h2/src/tools/org/h2/java/lang/Integer.java deleted file mode 100644 index 94e98755e9..0000000000 --- a/h2/src/tools/org/h2/java/lang/Integer.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.java.lang; - -/** - * A java.lang.Integer implementation. - */ -public class Integer { - - /** - * The smallest possible value. - */ - public static final int MIN_VALUE = 1 << 31; - - /** - * The largest possible value. - */ - public static final int MAX_VALUE = (int) ((1L << 31) - 1); - - /** - * Convert a value to a String. - * - * @param x the value - * @return the String - */ - public static String toString(int x) { - // c: wchar_t ch[20]; - // c: swprintf(ch, 20, L"%" PRId32, x); - // c: return STRING(ch); - // c: return; - if (x == MIN_VALUE) { - return String.wrap("-2147483648"); - } - char[] ch = new char[20]; - int i = 20 - 1, count = 0; - boolean negative; - if (x < 0) { - negative = true; - x = -x; - } else { - negative = false; - } - for (; i >= 0; i--) { - ch[i] = (char) ('0' + (x % 10)); - x /= 10; - count++; - if (x == 0) { - break; - } - } - if (negative) { - ch[--i] = '-'; - count++; - } - return new String(ch, i, count); - } - -} diff --git a/h2/src/tools/org/h2/java/lang/Long.java b/h2/src/tools/org/h2/java/lang/Long.java deleted file mode 100644 index fa99c22cd4..0000000000 --- a/h2/src/tools/org/h2/java/lang/Long.java +++ /dev/null @@ -1,62 +0,0 @@ -/* -/* - * 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.java.lang; - -/** - * A java.lang.Long implementation. - */ -public class Long { - - /** - * The smallest possible value. - */ - public static final long MIN_VALUE = 1L << 63; - - /** - * The largest possible value. - */ - public static final long MAX_VALUE = (1L << 63) - 1; - - /** - * Convert a value to a String. - * - * @param x the value - * @return the String - */ - public static String toString(long x) { - // c: wchar_t ch[30]; - // c: swprintf(ch, 30, L"%" PRId64, x); - // c: return STRING(ch); - // c: return; - if (x == MIN_VALUE) { - return String.wrap("-9223372036854775808"); - } - char[] ch = new char[30]; - int i = 30 - 1, count = 0; - boolean negative; - if (x < 0) { - negative = true; - x = -x; - } else { - negative = false; - } - for (; i >= 0; i--) { - ch[i] = (char) ('0' + (x % 10)); - x /= 10; - count++; - if (x == 0) { - break; - } - } - if (negative) { - ch[--i] = '-'; - count++; - } - return new String(ch, i, count); - } - -} diff --git a/h2/src/tools/org/h2/java/lang/Math.java b/h2/src/tools/org/h2/java/lang/Math.java deleted file mode 100644 index f32cc63669..0000000000 --- a/h2/src/tools/org/h2/java/lang/Math.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.java.lang; - -/** - * A java.lang.String implementation. - */ -public class Math { - - /** - * Get the larger of both values. - * - * @param a the first value - * @param b the second value - * @return the larger - */ - public static int max(int a, int b) { - return a > b ? a : b; - } - -} diff --git a/h2/src/tools/org/h2/java/lang/Object.java b/h2/src/tools/org/h2/java/lang/Object.java deleted file mode 100644 index 2f7fb39921..0000000000 --- a/h2/src/tools/org/h2/java/lang/Object.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.java.lang; - -/** - * A java.lang.Object implementation. - */ -public class Object { - - @Override - public int hashCode() { - return 0; - } - - public boolean equals(Object other) { - return other == this; - } - - @Override - public java.lang.String toString() { - return "?"; - } - -} diff --git a/h2/src/tools/org/h2/java/lang/String.java b/h2/src/tools/org/h2/java/lang/String.java deleted file mode 100644 index 7f316c6041..0000000000 --- a/h2/src/tools/org/h2/java/lang/String.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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.java.lang; - -import org.h2.java.Ignore; -import org.h2.java.Local; - -/* c: - -#include -#include -#include -#include -#include -#include -#define __STDC_FORMAT_MACROS -#include - -#define jvoid void -#define jboolean int8_t -#define jbyte int8_t -#define jchar wchar_t -#define jint int32_t -#define jlong int64_t -#define jfloat float -#define jdouble double -#define ujint uint32_t -#define ujlong uint64_t -#define true 1 -#define false 0 -#define null 0 - -#define STRING_REF(s) ptr \ - (new java_lang_String(ptr< array > \ - (new array(s, (jint) wcslen(s))))); - -#define STRING_PTR(s) new java_lang_String \ - (new array(s, (jint) wcslen(s))); - -class RefBase { -protected: - jint refCount; -public: - RefBase() { - refCount = 0; - } - void reference() { - refCount++; - } - void release() { - if (--refCount == 0) { - delete this; - } - } - virtual ~RefBase() { - } -}; -template class ptr { - T* pointer; -public: - explicit ptr(T* p=0) : pointer(p) { - if (p != 0) { - ((RefBase*)p)->reference(); - } - } - ptr(const ptr& p) : pointer(p.pointer) { - if (p.pointer != 0) { - ((RefBase*)p.pointer)->reference(); - } - } - ~ptr() { - if (pointer != 0) { - ((RefBase*)pointer)->release(); - } - } - ptr& operator= (const ptr& p) { - if (this != &p && pointer != p.pointer) { - if (pointer != 0) { - ((RefBase*)pointer)->release(); - } - pointer = p.pointer; - if (pointer != 0) { - ((RefBase*)pointer)->reference(); - } - } - return *this; - } - T& operator*() { - return *pointer; - } - T* getPointer() { - return pointer; - } - T* operator->() { - return pointer; - } - jboolean operator==(const ptr& p) { - return pointer == p->pointer; - } - jboolean operator==(const RefBase* t) { - return pointer == t; - } -}; -template class array : RefBase { - jint len; - T* data; -public: - array(const T* d, jint len) { - this->len = len; - data = new T[len]; - memcpy(data, d, sizeof(T) * len); - } - array(jint len) { - this->len = len; - data = new T[len]; - } - ~array() { - delete[] data; - } - T* getPointer() { - return data; - } - jint length() { - return len; - } - T& operator[](jint index) { - if (index < 0 || index >= len) { - throw "index set"; - } - return data[index]; - } - T& at(jint index) { - if (index < 0 || index >= len) { - throw "index set"; - } - return data[index]; - } -}; - -*/ - -/** - * A java.lang.String implementation. - */ -public class String { - - /** - * The character array. - */ - @Local - char[] chars; - - private int hash; - - public String(char[] chars) { - this.chars = new char[chars.length]; - System.arraycopy(chars, 0, this.chars, 0, chars.length); - } - - public String(char[] chars, int offset, int count) { - this.chars = new char[count]; - System.arraycopy(chars, offset, this.chars, 0, count); - } - - @Override - public int hashCode() { - int h = hash; - if (h == 0) { - int size = chars.length; - if (size != 0) { - for (int i = 0; i < size; i++) { - h = h * 31 + chars[i]; - } - hash = h; - } - } - return h; - } - - /** - * Get the length of the string. - * - * @return the length - */ - public int length() { - return chars.length; - } - - /** - * The toString method. - * - * @return the string - */ - public String toStringMethod() { - return this; - } - - /** - * Get the java.lang.String. - * - * @return the string - */ - @Ignore - public java.lang.String asString() { - return new java.lang.String(chars); - } - - /** - * Wrap a java.lang.String. - * - * @param x the string - * @return the object - */ - @Ignore - public static String wrap(java.lang.String x) { - return new String(x.toCharArray()); - } - -} diff --git a/h2/src/tools/org/h2/java/lang/StringBuilder.java b/h2/src/tools/org/h2/java/lang/StringBuilder.java deleted file mode 100644 index 3d7eb79f11..0000000000 --- a/h2/src/tools/org/h2/java/lang/StringBuilder.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.java.lang; - -/** - * A java.lang.String implementation. - */ -public class StringBuilder { - - private int length; - private char[] buffer; - - public StringBuilder(String s) { - char[] chars = s.chars; - int len = chars.length; - buffer = new char[len]; - System.arraycopy(chars, 0, buffer, 0, len); - this.length = len; - } - - public StringBuilder() { - buffer = new char[10]; - } - - /** - * Append the given value. - * - * @param x the value - * @return this - */ - public StringBuilder append(String x) { - int l = x.length(); - ensureCapacity(l); - System.arraycopy(x.chars, 0, buffer, length, l); - length += l; - return this; - } - - /** - * Append the given value. - * - * @param x the value - * @return this - */ - public StringBuilder append(int x) { - append(Integer.toString(x)); - return this; - } - - @Override - public java.lang.String toString() { - return new java.lang.String(buffer, 0, length); - } - - private void ensureCapacity(int plus) { - if (buffer.length < length + plus) { - char[] b = new char[Math.max(length + plus, buffer.length * 2)]; - System.arraycopy(buffer, 0, b, 0, length); - buffer = b; - } - } - -} diff --git a/h2/src/tools/org/h2/java/lang/System.java b/h2/src/tools/org/h2/java/lang/System.java deleted file mode 100644 index ba75438608..0000000000 --- a/h2/src/tools/org/h2/java/lang/System.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.java.lang; - -import java.io.PrintStream; - -/** - * A simple java.lang.System implementation. - */ -public class System { - - /** - * The stdout stream. - */ - public static PrintStream out; - - /** - * Copy data from the source to the target. - * Source and target may overlap. - * - * @param src the source array - * @param srcPos the first element in the source array - * @param dest the destination - * @param destPos the first element in the destination - * @param length the number of element to copy - */ - public static void arraycopy(char[] src, int srcPos, char[] dest, - int destPos, int length) { - /* c: - memmove(((jchar*)dest->getPointer()) + destPos, - ((jchar*)src->getPointer()) + srcPos, sizeof(jchar) * length); - */ - // c: return; - java.lang.System.arraycopy(src, srcPos, dest, destPos, length); - } - - /** - * Copy data from the source to the target. - * Source and target may overlap. - * - * @param src the source array - * @param srcPos the first element in the source array - * @param dest the destination - * @param destPos the first element in the destination - * @param length the number of element to copy - */ - public static void arraycopy(byte[] src, int srcPos, byte[] dest, - int destPos, int length) { - /* c: - memmove(((jbyte*)dest->getPointer()) + destPos, - ((jbyte*)src->getPointer()) + srcPos, sizeof(jbyte) * length); - */ - // c: return; - java.lang.System.arraycopy(src, srcPos, dest, destPos, length); - } - - /** - * Get the current time in milliseconds since 1970-01-01. - * - * @return the milliseconds - */ - public static long nanoTime() { - /* c: - #if CLOCKS_PER_SEC == 1000000 - return (jlong) clock() * 1000; - #else - return (jlong) clock() * 1000000 / CLOCKS_PER_SEC; - #endif - */ - // c: return; - return java.lang.System.nanoTime(); - } - -} diff --git a/h2/src/tools/org/h2/java/lang/package.html b/h2/src/tools/org/h2/java/lang/package.html deleted file mode 100644 index fb9167e95f..0000000000 --- a/h2/src/tools/org/h2/java/lang/package.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - -Javadoc package documentation -

- -A simple implementation of the java.lang.* package for the Java parser. - -

\ No newline at end of file diff --git a/h2/src/tools/org/h2/java/package.html b/h2/src/tools/org/h2/java/package.html deleted file mode 100644 index 0beb44f98c..0000000000 --- a/h2/src/tools/org/h2/java/package.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - -Javadoc package documentation -

- -A Java parser implementation. - -

\ No newline at end of file diff --git a/h2/src/tools/org/h2/java/util/Arrays.java b/h2/src/tools/org/h2/java/util/Arrays.java deleted file mode 100644 index 463625c980..0000000000 --- a/h2/src/tools/org/h2/java/util/Arrays.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.java.util; - -/** - * An simple implementation of java.util.Arrays - */ -public class Arrays { - - /** - * Fill an array with the given value. - * - * @param array the array - * @param x the value - */ - public static void fill(char[] array, char x) { - for (int i = 0, size = array.length; i < size; i++) { - array[i] = x; - } - } - - /** - * Fill an array with the given value. - * - * @param array the array - * @param x the value - */ - public static void fill(byte[] array, byte x) { - for (int i = 0; i < array.length; i++) { - array[i] = x; - } - } - - /** - * Fill an array with the given value. - * - * @param array the array - * @param x the value - */ - public static void fill(int[] array, int x) { - for (int i = 0; i < array.length; i++) { - array[i] = x; - } - } - - - /** - * Fill an array with the given value. - * - * @param array the array - * @param x the value - */ - public static void fillByte(byte[] array, byte x) { - for (int i = 0; i < array.length; i++) { - array[i] = x; - } - } - - /** - * Fill an array with the given value. - * - * @param array the array - * @param x the value - */ - public static void fillInt(int[] array, int x) { - for (int i = 0; i < array.length; i++) { - array[i] = x; - } - } - -} diff --git a/h2/src/tools/org/h2/java/util/package.html b/h2/src/tools/org/h2/java/util/package.html deleted file mode 100644 index fb9167e95f..0000000000 --- a/h2/src/tools/org/h2/java/util/package.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - -Javadoc package documentation -

- -A simple implementation of the java.lang.* package for the Java parser. - -

\ No newline at end of file From 8aeb3d787ab718f224b0a5b052b91aea8463cd57 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 18 Jan 2022 18:55:21 +0800 Subject: [PATCH 06/93] Really use DateTimeFormatter with time zone in DateTimeFormatFunction --- .../main/org/h2/expression/function/DateTimeFormatFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java b/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java index e426807e91..361836ebe9 100644 --- a/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java +++ b/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java @@ -262,7 +262,7 @@ private static CacheValue getDateFormat(String format, String locale, String tim ZoneId zoneId; if (timeZone != null) { zoneId = getZoneId(timeZone); - df.withZone(zoneId); + df = df.withZone(zoneId); } else { zoneId = null; } From 1e6944698c98a0db8534770ece2052c5ffe8a85b Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 18 Jan 2022 19:30:29 +0800 Subject: [PATCH 07/93] Fix some warnings from Sonatype Lift --- h2/src/main/org/h2/bnf/Bnf.java | 6 ++++-- h2/src/main/org/h2/command/Parser.java | 8 ++++---- h2/src/main/org/h2/command/dml/Help.java | 4 +++- h2/src/main/org/h2/engine/Database.java | 2 +- h2/src/main/org/h2/engine/SessionLocal.java | 2 +- h2/src/main/org/h2/engine/SettingsBase.java | 3 ++- .../main/org/h2/fulltext/FullTextSettings.java | 17 ++++++++--------- .../main/org/h2/jdbcx/JdbcConnectionPool.java | 2 +- h2/src/main/org/h2/message/Trace.java | 2 +- h2/src/main/org/h2/mode/PgCatalogTable.java | 7 +++---- h2/src/main/org/h2/mvstore/DataUtils.java | 2 +- h2/src/main/org/h2/mvstore/db/Store.java | 2 +- .../main/org/h2/mvstore/db/ValueDataType.java | 2 +- .../org/h2/mvstore/type/ObjectDataType.java | 6 ++---- h2/src/main/org/h2/security/AES.java | 2 +- h2/src/main/org/h2/security/CipherFactory.java | 5 +---- .../impl/StaticUserCredentialsValidator.java | 5 +++-- .../main/org/h2/server/pg/PgServerThread.java | 2 +- h2/src/main/org/h2/server/web/WebServer.java | 5 +++-- h2/src/main/org/h2/store/Data.java | 2 +- .../h2/table/InformationSchemaTableLegacy.java | 4 ++-- h2/src/main/org/h2/table/Table.java | 2 +- h2/src/main/org/h2/table/TableFilter.java | 2 +- h2/src/main/org/h2/tools/MultiDimension.java | 2 +- h2/src/main/org/h2/util/MathUtils.java | 4 ++-- h2/src/main/org/h2/util/SortedProperties.java | 12 ++++++------ h2/src/main/org/h2/util/StringUtils.java | 4 ++-- .../main/org/h2/util/json/JSONBytesSource.java | 4 ++-- h2/src/main/org/h2/value/ValueBinary.java | 2 +- h2/src/main/org/h2/value/ValueJavaObject.java | 2 +- h2/src/main/org/h2/value/ValueTime.java | 2 +- h2/src/main/org/h2/value/ValueUuid.java | 2 +- h2/src/main/org/h2/value/ValueVarbinary.java | 2 +- .../org/h2/value/lob/LobDataFetchOnDemand.java | 2 +- 34 files changed, 66 insertions(+), 66 deletions(-) diff --git a/h2/src/main/org/h2/bnf/Bnf.java b/h2/src/main/org/h2/bnf/Bnf.java index 3faccea4e4..c32ab4860b 100644 --- a/h2/src/main/org/h2/bnf/Bnf.java +++ b/h2/src/main/org/h2/bnf/Bnf.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -52,7 +53,7 @@ public static Bnf getInstance(Reader csv) throws SQLException, IOException { Bnf bnf = new Bnf(); if (csv == null) { byte[] data = Utils.getResource("/org/h2/res/help.csv"); - csv = new InputStreamReader(new ByteArrayInputStream(data)); + csv = new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8); } bnf.parse(csv); return bnf; @@ -168,7 +169,8 @@ public static boolean startWithSpace(String s) { */ public static String getRuleMapKey(String token) { StringBuilder buff = new StringBuilder(); - for (char ch : token.toCharArray()) { + for (int i = 0, l = token.length(); i < l; i++) { + char ch = token.charAt(i); if (Character.isUpperCase(ch)) { buff.append('_').append(Character.toLowerCase(ch)); } else { diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 6aa8a51d37..505f24844a 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -641,12 +641,12 @@ Prepared parse(String sql, ArrayList tokens) { Prepared p; try { // first, try the fast variant - p = parse(sql, false); + p = parse(false); } catch (DbException e) { if (e.getErrorCode() == ErrorCode.SYNTAX_ERROR_1) { // now, get the detailed exception resetTokenIndex(); - p = parse(sql, true); + p = parse(true); } else { throw e.addSQL(sql); } @@ -656,7 +656,7 @@ Prepared parse(String sql, ArrayList tokens) { return p; } - private Prepared parse(String sql, boolean withExpectedList) { + private Prepared parse(boolean withExpectedList) { if (withExpectedList) { expectedList = new ArrayList<>(); } else { @@ -5437,7 +5437,7 @@ && equalsToken("E", name)) { break; case 'U': if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR - && (equalsToken("UUID", name))) { + && equalsToken("UUID", name)) { String uuid = token.value(session).getString(); read(); return ValueExpression.get(ValueUuid.get(uuid)); diff --git a/h2/src/main/org/h2/command/dml/Help.java b/h2/src/main/org/h2/command/dml/Help.java index 528909e31d..338e2f1b20 100644 --- a/h2/src/main/org/h2/command/dml/Help.java +++ b/h2/src/main/org/h2/command/dml/Help.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.sql.ResultSet; import org.h2.command.CommandInterface; @@ -127,7 +128,8 @@ public static String processHelpText(String s) { * on I/O exception */ public static ResultSet getTable() throws IOException { - Reader reader = new InputStreamReader(new ByteArrayInputStream(Utils.getResource("/org/h2/res/help.csv"))); + Reader reader = new InputStreamReader(new ByteArrayInputStream(Utils.getResource("/org/h2/res/help.csv")), + StandardCharsets.UTF_8); Csv csv = new Csv(); csv.setLineCommentCharacter('#'); return csv.read(reader, null); diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index f7d6958c4e..6f49bec122 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -208,7 +208,7 @@ public final class Database implements DataHandler, CastDataProvider { private JavaObjectSerializer javaObjectSerializer; private String javaObjectSerializerName; private volatile boolean javaObjectSerializerInitialized; - private boolean queryStatistics; + private volatile boolean queryStatistics; private int queryStatisticsMaxEntries = Constants.QUERY_STATISTICS_MAX_ENTRIES; private QueryStatisticsData queryStatisticsData; private RowFactory rowFactory = RowFactory.getRowFactory(); diff --git a/h2/src/main/org/h2/engine/SessionLocal.java b/h2/src/main/org/h2/engine/SessionLocal.java index 8117c628da..753ee14465 100644 --- a/h2/src/main/org/h2/engine/SessionLocal.java +++ b/h2/src/main/org/h2/engine/SessionLocal.java @@ -630,7 +630,7 @@ public Command prepareLocal(String sql) { * at the end of the current transaction. * @param id to be scheduled */ - protected void scheduleDatabaseObjectIdForRelease(int id) { + void scheduleDatabaseObjectIdForRelease(int id) { if (idsToRelease == null) { idsToRelease = new BitSet(); } diff --git a/h2/src/main/org/h2/engine/SettingsBase.java b/h2/src/main/org/h2/engine/SettingsBase.java index 2059dfdbb6..bfca5522fb 100644 --- a/h2/src/main/org/h2/engine/SettingsBase.java +++ b/h2/src/main/org/h2/engine/SettingsBase.java @@ -84,7 +84,8 @@ protected String get(String key, String defaultValue) { } StringBuilder buff = new StringBuilder("h2."); boolean nextUpper = false; - for (char c : key.toCharArray()) { + for (int i = 0, l = key.length(); i < l; i++) { + char c = key.charAt(i); if (c == '_') { nextUpper = true; } else { diff --git a/h2/src/main/org/h2/fulltext/FullTextSettings.java b/h2/src/main/org/h2/fulltext/FullTextSettings.java index 7cdfc2841c..8b867e4b3d 100644 --- a/h2/src/main/org/h2/fulltext/FullTextSettings.java +++ b/h2/src/main/org/h2/fulltext/FullTextSettings.java @@ -203,8 +203,7 @@ private static String getIndexPath(Connection conn) throws SQLException { * @return the prepared statement * @throws SQLException on failure */ - synchronized PreparedStatement prepare(Connection conn, String sql) - throws SQLException { + synchronized PreparedStatement prepare(Connection conn, String sql) throws SQLException { SoftValuesHashMap c = cache.get(conn); if (c == null) { c = new SoftValuesHashMap<>(); @@ -224,7 +223,7 @@ synchronized PreparedStatement prepare(Connection conn, String sql) /** * Remove all indexes from the settings. */ - protected void removeAllIndexes() { + void removeAllIndexes() { indexes.clear(); } @@ -233,7 +232,7 @@ protected void removeAllIndexes() { * * @param index the index to remove */ - protected void removeIndexInfo(IndexInfo index) { + void removeIndexInfo(IndexInfo index) { indexes.remove(index.id); } @@ -242,7 +241,7 @@ protected void removeIndexInfo(IndexInfo index) { * * @param b the new value */ - protected void setInitialized(boolean b) { + void setInitialized(boolean b) { this.initialized = b; } @@ -251,24 +250,24 @@ protected void setInitialized(boolean b) { * * @return whether this instance is initialized */ - protected boolean isInitialized() { + boolean isInitialized() { return initialized; } /** * Close all fulltext settings, freeing up memory. */ - protected static void closeAll() { + static void closeAll() { synchronized (SETTINGS) { SETTINGS.clear(); } } - protected void setWhitespaceChars(String whitespaceChars) { + void setWhitespaceChars(String whitespaceChars) { this.whitespaceChars = whitespaceChars; } - protected String getWhitespaceChars() { + String getWhitespaceChars() { return whitespaceChars; } diff --git a/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java b/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java index 0ff22cd22f..705060fbcc 100644 --- a/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java +++ b/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java @@ -77,7 +77,7 @@ public final class JdbcConnectionPool private AtomicInteger activeConnections = new AtomicInteger(); private AtomicBoolean isDisposed = new AtomicBoolean(); - protected JdbcConnectionPool(ConnectionPoolDataSource dataSource) { + private JdbcConnectionPool(ConnectionPoolDataSource dataSource) { this.dataSource = dataSource; if (dataSource != null) { try { diff --git a/h2/src/main/org/h2/message/Trace.java b/h2/src/main/org/h2/message/Trace.java index 066d026cdd..dd84fd61a0 100644 --- a/h2/src/main/org/h2/message/Trace.java +++ b/h2/src/main/org/h2/message/Trace.java @@ -94,7 +94,7 @@ public final class Trace { /** * Module names by their ids as array indexes. */ - public static final String[] MODULE_NAMES = { + static final String[] MODULE_NAMES = { "command", "constraint", "database", diff --git a/h2/src/main/org/h2/mode/PgCatalogTable.java b/h2/src/main/org/h2/mode/PgCatalogTable.java index 161da669a1..ac7afd020a 100644 --- a/h2/src/main/org/h2/mode/PgCatalogTable.java +++ b/h2/src/main/org/h2/mode/PgCatalogTable.java @@ -588,7 +588,7 @@ private void pgAttribute(SessionLocal session, ArrayList rows, Table table) int tableId = table.getId(); for (int i = 0; i < cols.length;) { Column column = cols[i++]; - addAttribute(session, rows, tableId * 10_000 + i, tableId, table, column, i); + addAttribute(session, rows, tableId * 10_000 + i, tableId, column, i); } for (Index index : table.getIndexes()) { if (index.getCreateSQL() == null) { @@ -598,8 +598,7 @@ private void pgAttribute(SessionLocal session, ArrayList rows, Table table) for (int i = 0; i < cols.length;) { Column column = cols[i++]; int indexId = index.getId(); - addAttribute(session, rows, 1_000_000 * indexId + tableId * 10_000 + i, indexId, table, column, - i); + addAttribute(session, rows, 1_000_000 * indexId + tableId * 10_000 + i, indexId, column, i); } } } @@ -656,7 +655,7 @@ private void pgConstraint(SessionLocal session, ArrayList rows) { } } - private void addAttribute(SessionLocal session, ArrayList rows, int id, int relId, Table table, Column column, + private void addAttribute(SessionLocal session, ArrayList rows, int id, int relId, Column column, int ordinal) { long precision = column.getType().getPrecision(); add(session, rows, diff --git a/h2/src/main/org/h2/mvstore/DataUtils.java b/h2/src/main/org/h2/mvstore/DataUtils.java index 872e7b79e6..c784b8c88e 100644 --- a/h2/src/main/org/h2/mvstore/DataUtils.java +++ b/h2/src/main/org/h2/mvstore/DataUtils.java @@ -331,7 +331,7 @@ public static void writeStringData(ByteBuffer buff, buff.put((byte) c); } else if (c >= 0x800) { buff.put((byte) (0xe0 | (c >> 12))); - buff.put((byte) (((c >> 6) & 0x3f))); + buff.put((byte) ((c >> 6) & 0x3f)); buff.put((byte) (c & 0x3f)); } else { buff.put((byte) (0xc0 | (c >> 6))); diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index 6f5b5befcf..35c3a58207 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -48,7 +48,7 @@ public final class Store { static char[] decodePassword(byte[] key) { char[] password = new char[key.length / 2]; for (int i = 0; i < password.length; i++) { - password[i] = (char) (((key[i + i] & 255) << 16) | ((key[i + i + 1]) & 255)); + password[i] = (char) (((key[i + i] & 255) << 16) | (key[i + i + 1] & 255)); } return password; } diff --git a/h2/src/main/org/h2/mvstore/db/ValueDataType.java b/h2/src/main/org/h2/mvstore/db/ValueDataType.java index 36d4ccbe0f..1d4b449310 100644 --- a/h2/src/main/org/h2/mvstore/db/ValueDataType.java +++ b/h2/src/main/org/h2/mvstore/db/ValueDataType.java @@ -528,7 +528,7 @@ public void write(WriteBuffer buff, Value v) { ordinal = ~ordinal; } buff.put(INTERVAL). - put((byte) (ordinal)). + put((byte) ordinal). putVarLong(interval.getLeading()). putVarLong(interval.getRemaining()); break; diff --git a/h2/src/main/org/h2/mvstore/type/ObjectDataType.java b/h2/src/main/org/h2/mvstore/type/ObjectDataType.java index 3b41c930d8..eab21c0d08 100644 --- a/h2/src/main/org/h2/mvstore/type/ObjectDataType.java +++ b/h2/src/main/org/h2/mvstore/type/ObjectDataType.java @@ -1358,11 +1358,9 @@ public int compare(Object aObj, Object bObj) { x = Integer.signum((((boolean[]) aObj)[i] ? 1 : 0) - (((boolean[]) bObj)[i] ? 1 : 0)); } else if (type == char.class) { - x = Integer.signum((((char[]) aObj)[i]) - - (((char[]) bObj)[i])); + x = Integer.signum(((char[]) aObj)[i] - ((char[]) bObj)[i]); } else if (type == short.class) { - x = Integer.signum((((short[]) aObj)[i]) - - (((short[]) bObj)[i])); + x = Integer.signum(((short[]) aObj)[i] - ((short[]) bObj)[i]); } else if (type == int.class) { int a = ((int[]) aObj)[i]; int b = ((int[]) bObj)[i]; diff --git a/h2/src/main/org/h2/security/AES.java b/h2/src/main/org/h2/security/AES.java index 24a73257f8..2644770f62 100644 --- a/h2/src/main/org/h2/security/AES.java +++ b/h2/src/main/org/h2/security/AES.java @@ -96,7 +96,7 @@ public void setKey(byte[] key) { encKey[e + 4] = encKey[e] ^ RCON[i] ^ (FS[(encKey[e + 3] >> 16) & 255] << 24) ^ (FS[(encKey[e + 3] >> 8) & 255] << 16) - ^ (FS[(encKey[e + 3]) & 255] << 8) + ^ (FS[encKey[e + 3] & 255] << 8) ^ FS[(encKey[e + 3] >> 24) & 255]; encKey[e + 5] = encKey[e + 1] ^ encKey[e + 4]; encKey[e + 6] = encKey[e + 2] ^ encKey[e + 5]; diff --git a/h2/src/main/org/h2/security/CipherFactory.java b/h2/src/main/org/h2/security/CipherFactory.java index 0477e9afa7..8096238bd6 100644 --- a/h2/src/main/org/h2/security/CipherFactory.java +++ b/h2/src/main/org/h2/security/CipherFactory.java @@ -141,7 +141,6 @@ public static Socket createSocket(InetAddress address, int port) */ public static ServerSocket createServerSocket(int port, InetAddress bindAddress) throws IOException { - ServerSocket socket = null; if (SysProperties.ENABLE_ANONYMOUS_TLS) { removeAnonFromLegacyAlgorithms(); } @@ -161,9 +160,7 @@ public static ServerSocket createServerSocket(int port, secureSocket.getSupportedCipherSuites()); secureSocket.setEnabledCipherSuites(list); } - - socket = secureSocket; - return socket; + return secureSocket; } /** diff --git a/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java b/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java index edee8de558..6296328dd8 100644 --- a/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java +++ b/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java @@ -5,6 +5,7 @@ */ package org.h2.security.auth.impl; +import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; import org.h2.api.CredentialsValidator; @@ -36,7 +37,7 @@ public StaticUserCredentialsValidator(String userNamePattern,String password) { this.userNamePattern=Pattern.compile(userNamePattern.toUpperCase()); } salt=MathUtils.secureRandomBytes(256); - hashWithSalt=SHA256.getHashWithSalt(password.getBytes(), salt); + hashWithSalt=SHA256.getHashWithSalt(password.getBytes(StandardCharsets.UTF_8), salt); } @Override @@ -50,7 +51,7 @@ public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws return password.equals(authenticationInfo.getPassword()); } return Utils.compareSecure(hashWithSalt, - SHA256.getHashWithSalt(authenticationInfo.getPassword().getBytes(), salt)); + SHA256.getHashWithSalt(authenticationInfo.getPassword().getBytes(StandardCharsets.UTF_8), salt)); } @Override diff --git a/h2/src/main/org/h2/server/pg/PgServerThread.java b/h2/src/main/org/h2/server/pg/PgServerThread.java index aba652af6a..4e243f8a8d 100644 --- a/h2/src/main/org/h2/server/pg/PgServerThread.java +++ b/h2/src/main/org/h2/server/pg/PgServerThread.java @@ -705,7 +705,7 @@ private void writeDataColumn(Value v, int pgType, boolean text) throws IOExcepti } case PgServer.PG_TYPE_DATE: writeInt(4); - writeInt((int) (toPostgreDays(((ValueDate) v).getDateValue()))); + writeInt((int) toPostgreDays(((ValueDate) v).getDateValue())); break; case PgServer.PG_TYPE_TIME: writeTimeBinary(((ValueTime) v).getNanos(), 8); diff --git a/h2/src/main/org/h2/server/web/WebServer.java b/h2/src/main/org/h2/server/web/WebServer.java index 73d17644da..70434179fb 100644 --- a/h2/src/main/org/h2/server/web/WebServer.java +++ b/h2/src/main/org/h2/server/web/WebServer.java @@ -522,8 +522,9 @@ void readTranslations(WebSession session, String language) { try { trace("translation: "+language); byte[] trans = getFile("_text_"+language+".prop"); - trace(" "+new String(trans)); - text = SortedProperties.fromLines(new String(trans, StandardCharsets.UTF_8)); + String s = new String(trans, StandardCharsets.UTF_8); + trace(" " + s); + text = SortedProperties.fromLines(s); // remove starting # (if not translated yet) for (Entry entry : text.entrySet()) { String value = (String) entry.getValue(); diff --git a/h2/src/main/org/h2/store/Data.java b/h2/src/main/org/h2/store/Data.java index 76136b935e..67316a0c7f 100644 --- a/h2/src/main/org/h2/store/Data.java +++ b/h2/src/main/org/h2/store/Data.java @@ -72,7 +72,7 @@ private void writeStringWithoutLength(char[] chars, int len) { buff[p++] = (byte) c; } else if (c >= 0x800) { buff[p++] = (byte) (0xe0 | (c >> 12)); - buff[p++] = (byte) (((c >> 6) & 0x3f)); + buff[p++] = (byte) ((c >> 6) & 0x3f); buff[p++] = (byte) (c & 0x3f); } else { buff[p++] = (byte) (0xc0 | (c >> 6)); diff --git a/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java b/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java index e55ec11929..59695ef8a2 100644 --- a/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java +++ b/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.Types; @@ -1166,8 +1167,7 @@ public ArrayList generateRows(SessionLocal session, SearchRow first, Search String resource = "/org/h2/res/help.csv"; try { final byte[] data = Utils.getResource(resource); - final Reader reader = new InputStreamReader( - new ByteArrayInputStream(data)); + final Reader reader = new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8); final Csv csv = new Csv(); csv.setLineCommentCharacter('#'); final ResultSet rs = csv.read(reader, null); diff --git a/h2/src/main/org/h2/table/Table.java b/h2/src/main/org/h2/table/Table.java index c2b5b14fbc..d194c64015 100644 --- a/h2/src/main/org/h2/table/Table.java +++ b/h2/src/main/org/h2/table/Table.java @@ -760,7 +760,7 @@ public Column getColumn(String columnName) { * Get the column with the given name. * * @param columnName the column name - * @param ifExists if (@code true) return {@code null} if column does not exist + * @param ifExists if {@code true} return {@code null} if column does not exist * @return the column * @throws DbException if the column was not found */ diff --git a/h2/src/main/org/h2/table/TableFilter.java b/h2/src/main/org/h2/table/TableFilter.java index 990467a718..cd0a952be6 100644 --- a/h2/src/main/org/h2/table/TableFilter.java +++ b/h2/src/main/org/h2/table/TableFilter.java @@ -964,7 +964,7 @@ public boolean hasDerivedColumnList() { * @param columnName * the column name * @param ifExists - * if (@code true) return {@code null} if column does not exist + * if {@code true} return {@code null} if column does not exist * @return the column * @throws DbException * if the column was not found and {@code ifExists} is diff --git a/h2/src/main/org/h2/tools/MultiDimension.java b/h2/src/main/org/h2/tools/MultiDimension.java index 7c694d576d..591348f6a6 100644 --- a/h2/src/main/org/h2/tools/MultiDimension.java +++ b/h2/src/main/org/h2/tools/MultiDimension.java @@ -310,7 +310,7 @@ private void addMortonRanges(ArrayList list, int[] min, int[] max, } private static int roundUp(int x, int blockSizePowerOf2) { - return (x + blockSizePowerOf2 - 1) & (-blockSizePowerOf2); + return (x + blockSizePowerOf2 - 1) & -blockSizePowerOf2; } private static int findMiddle(int a, int b) { diff --git a/h2/src/main/org/h2/util/MathUtils.java b/h2/src/main/org/h2/util/MathUtils.java index 2a84beb7ff..3edb1dec9e 100644 --- a/h2/src/main/org/h2/util/MathUtils.java +++ b/h2/src/main/org/h2/util/MathUtils.java @@ -44,7 +44,7 @@ private MathUtils() { * @return the rounded value */ public static int roundUpInt(int x, int blockSizePowerOf2) { - return (x + blockSizePowerOf2 - 1) & (-blockSizePowerOf2); + return (x + blockSizePowerOf2 - 1) & -blockSizePowerOf2; } /** @@ -58,7 +58,7 @@ public static int roundUpInt(int x, int blockSizePowerOf2) { * @return the rounded value */ public static long roundUpLong(long x, long blockSizePowerOf2) { - return (x + blockSizePowerOf2 - 1) & (-blockSizePowerOf2); + return (x + blockSizePowerOf2 - 1) & -blockSizePowerOf2; } private static synchronized SecureRandom getSecureRandom() { diff --git a/h2/src/main/org/h2/util/SortedProperties.java b/h2/src/main/org/h2/util/SortedProperties.java index 7989dd83ae..f7dfd7697a 100644 --- a/h2/src/main/org/h2/util/SortedProperties.java +++ b/h2/src/main/org/h2/util/SortedProperties.java @@ -16,12 +16,12 @@ import java.io.PrintWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Map.Entry; import java.util.Properties; import java.util.TreeMap; -import java.util.Vector; import org.h2.store.fs.FileUtils; /** @@ -34,12 +34,12 @@ public class SortedProperties extends Properties { @Override public synchronized Enumeration keys() { - Vector v = new Vector<>(); + ArrayList v = new ArrayList<>(); for (Object o : keySet()) { v.add(o.toString()); } - Collections.sort(v); - return new Vector(v).elements(); + v.sort(null); + return Collections.enumeration(v); } /** @@ -102,7 +102,7 @@ public static synchronized SortedProperties loadProperties(String fileName) SortedProperties prop = new SortedProperties(); if (FileUtils.exists(fileName)) { try (InputStream in = FileUtils.newInputStream(fileName)) { - prop.load(in); + prop.load(new InputStreamReader(in, StandardCharsets.ISO_8859_1)); } } return prop; @@ -122,7 +122,7 @@ public synchronized void store(String fileName) throws IOException { LineNumberReader r = new LineNumberReader(reader); Writer w; try { - w = new OutputStreamWriter(FileUtils.newOutputStream(fileName, false)); + w = new OutputStreamWriter(FileUtils.newOutputStream(fileName, false), StandardCharsets.ISO_8859_1); } catch (Exception e) { throw new IOException(e.toString(), e); } diff --git a/h2/src/main/org/h2/util/StringUtils.java b/h2/src/main/org/h2/util/StringUtils.java index 85bca6b51f..3faef45a67 100644 --- a/h2/src/main/org/h2/util/StringUtils.java +++ b/h2/src/main/org/h2/util/StringUtils.java @@ -347,7 +347,7 @@ public static String javaDecode(String s) { throw getFormatException(s, i); } try { - c = (char) (Integer.parseInt(s.substring(i + 1, i + 5), 16)); + c = (char) Integer.parseInt(s.substring(i + 1, i + 5), 16); } catch (NumberFormatException e) { throw getFormatException(s, i); } @@ -358,7 +358,7 @@ public static String javaDecode(String s) { default: if (c >= '0' && c <= '9' && i + 2 < length) { try { - c = (char) (Integer.parseInt(s.substring(i, i + 3), 8)); + c = (char) Integer.parseInt(s.substring(i, i + 3), 8); } catch (NumberFormatException e) { throw getFormatException(s, i); } diff --git a/h2/src/main/org/h2/util/json/JSONBytesSource.java b/h2/src/main/org/h2/util/json/JSONBytesSource.java index bb42c32fcd..2f9c75b83e 100644 --- a/h2/src/main/org/h2/util/json/JSONBytesSource.java +++ b/h2/src/main/org/h2/util/json/JSONBytesSource.java @@ -169,7 +169,7 @@ void parseNumber(boolean positive) { index = skipInt(index, false); } } - target.valueNumber(new BigDecimal(new String(bytes, start, index - start))); + target.valueNumber(new BigDecimal(new String(bytes, start, index - start, StandardCharsets.ISO_8859_1))); this.index = index; } @@ -241,7 +241,7 @@ char readHex() { } int ch; try { - ch = Integer.parseInt(new String(bytes, index, 4), 16); + ch = Integer.parseInt(new String(bytes, index, 4, StandardCharsets.ISO_8859_1), 16); } catch (NumberFormatException e) { throw new IllegalArgumentException(); } diff --git a/h2/src/main/org/h2/value/ValueBinary.java b/h2/src/main/org/h2/value/ValueBinary.java index ef160e4665..f20a5a71f1 100644 --- a/h2/src/main/org/h2/value/ValueBinary.java +++ b/h2/src/main/org/h2/value/ValueBinary.java @@ -22,7 +22,7 @@ public final class ValueBinary extends ValueBytesBase { */ private TypeInfo type; - protected ValueBinary(byte[] value) { + private ValueBinary(byte[] value) { super(value); int length = value.length; if (length > Constants.MAX_STRING_LENGTH) { diff --git a/h2/src/main/org/h2/value/ValueJavaObject.java b/h2/src/main/org/h2/value/ValueJavaObject.java index 9eb3a75d29..8629a02f9c 100644 --- a/h2/src/main/org/h2/value/ValueJavaObject.java +++ b/h2/src/main/org/h2/value/ValueJavaObject.java @@ -19,7 +19,7 @@ public final class ValueJavaObject extends ValueBytesBase { private static final ValueJavaObject EMPTY = new ValueJavaObject(Utils.EMPTY_BYTES); - protected ValueJavaObject(byte[] v) { + private ValueJavaObject(byte[] v) { super(v); int length = value.length; if (length > Constants.MAX_STRING_LENGTH) { diff --git a/h2/src/main/org/h2/value/ValueTime.java b/h2/src/main/org/h2/value/ValueTime.java index c4ac3a1881..4a22df9c9f 100644 --- a/h2/src/main/org/h2/value/ValueTime.java +++ b/h2/src/main/org/h2/value/ValueTime.java @@ -112,7 +112,7 @@ public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) @Override public boolean equals(Object other) { - return this == other || other instanceof ValueTime && nanos == (((ValueTime) other).nanos); + return this == other || other instanceof ValueTime && nanos == ((ValueTime) other).nanos; } @Override diff --git a/h2/src/main/org/h2/value/ValueUuid.java b/h2/src/main/org/h2/value/ValueUuid.java index ca5fa3d73e..9997abea1a 100644 --- a/h2/src/main/org/h2/value/ValueUuid.java +++ b/h2/src/main/org/h2/value/ValueUuid.java @@ -51,7 +51,7 @@ public static ValueUuid getNewRandom() { long high = MathUtils.secureRandomLong(); long low = MathUtils.secureRandomLong(); // version 4 (random) - high = (high & (~0xf000L)) | 0x4000L; + high = (high & ~0xf000L) | 0x4000L; // variant (Leach-Salz) low = (low & 0x3fff_ffff_ffff_ffffL) | 0x8000_0000_0000_0000L; return new ValueUuid(high, low); diff --git a/h2/src/main/org/h2/value/ValueVarbinary.java b/h2/src/main/org/h2/value/ValueVarbinary.java index b0d5344432..12fa6bee16 100644 --- a/h2/src/main/org/h2/value/ValueVarbinary.java +++ b/h2/src/main/org/h2/value/ValueVarbinary.java @@ -27,7 +27,7 @@ public final class ValueVarbinary extends ValueBytesBase { */ private TypeInfo type; - protected ValueVarbinary(byte[] value) { + private ValueVarbinary(byte[] value) { super(value); int length = value.length; if (length > Constants.MAX_STRING_LENGTH) { diff --git a/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java b/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java index 4b3f50c218..ee1bc7e062 100644 --- a/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java +++ b/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java @@ -33,7 +33,7 @@ public final class LobDataFetchOnDemand extends LobData { * hmac acts a security cookie that the client can send back to the server * to ask for data related to this LOB. */ - protected final byte[] hmac; + private final byte[] hmac; public LobDataFetchOnDemand(DataHandler handler, int tableId, long lobId, byte[] hmac) { this.hmac = hmac; From defff8b66e4736ebf813fba9b9668aff4e14fe5e Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 22 Jan 2022 11:49:11 +0800 Subject: [PATCH 08/93] Don't try to prepare data change command second time --- h2/src/docsrc/html/changelog.html | 4 +++ .../h2/command/dml/DataChangeStatement.java | 13 ++++++++++ h2/src/main/org/h2/command/dml/Delete.java | 3 ++- h2/src/main/org/h2/command/dml/Insert.java | 3 ++- h2/src/main/org/h2/command/dml/Merge.java | 3 ++- .../main/org/h2/command/dml/MergeUsing.java | 2 +- h2/src/main/org/h2/command/dml/Update.java | 2 +- h2/src/main/org/h2/command/query/Query.java | 16 +++++++++++- h2/src/main/org/h2/command/query/Select.java | 10 +------- .../org/h2/command/query/SelectUnion.java | 10 +------- .../command/query/TableValueConstructor.java | 10 +------- .../test/org/h2/test/db/TestMergeUsing.java | 25 +++++++++++++++++++ 12 files changed, 68 insertions(+), 33 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 9750b5f4af..1275aa0ff6 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,6 +21,10 @@

Change Log

Next Version (unreleased)

    +
  • Issue #3391: Hang on merge statement with data change delta table +
  • +
  • PR #3384: Remove abandoned Java to C converter and fix some warnings from Sonatype Lift +
  • PR #3382: Use secure parser in H2AuthConfigXml to avoid future reports
diff --git a/h2/src/main/org/h2/command/dml/DataChangeStatement.java b/h2/src/main/org/h2/command/dml/DataChangeStatement.java index a2b53970f4..f3544cc0bc 100644 --- a/h2/src/main/org/h2/command/dml/DataChangeStatement.java +++ b/h2/src/main/org/h2/command/dml/DataChangeStatement.java @@ -17,6 +17,8 @@ */ public abstract class DataChangeStatement extends Prepared { + private boolean isPrepared; + /** * Creates new instance of DataChangeStatement. * @@ -27,6 +29,17 @@ protected DataChangeStatement(SessionLocal session) { super(session); } + @Override + public final void prepare() { + if (isPrepared) { + return; + } + doPrepare(); + isPrepared = true; + } + + abstract void doPrepare(); + /** * Return the name of this statement. * diff --git a/h2/src/main/org/h2/command/dml/Delete.java b/h2/src/main/org/h2/command/dml/Delete.java index 832ba22dc2..ac229a3cb2 100644 --- a/h2/src/main/org/h2/command/dml/Delete.java +++ b/h2/src/main/org/h2/command/dml/Delete.java @@ -106,7 +106,7 @@ public String getPlanSQL(int sqlFlags) { } @Override - public void prepare() { + void doPrepare() { if (condition != null) { condition.mapColumns(targetTableFilter, 0, Expression.MAP_INITIAL); condition = condition.optimizeCondition(session); @@ -137,4 +137,5 @@ public void collectDependencies(HashSet dependencies) { condition.isEverything(visitor); } } + } diff --git a/h2/src/main/org/h2/command/dml/Insert.java b/h2/src/main/org/h2/command/dml/Insert.java index aa350cc3ee..724a19aa6a 100644 --- a/h2/src/main/org/h2/command/dml/Insert.java +++ b/h2/src/main/org/h2/command/dml/Insert.java @@ -280,7 +280,7 @@ public String getPlanSQL(int sqlFlags) { } @Override - public void prepare() { + void doPrepare() { if (columns == null) { if (!valuesExpressionList.isEmpty() && valuesExpressionList.get(0).length == 0) { // special case where table is used as a sequence @@ -452,4 +452,5 @@ public void collectDependencies(HashSet dependencies) { query.isEverything(visitor); } } + } diff --git a/h2/src/main/org/h2/command/dml/Merge.java b/h2/src/main/org/h2/command/dml/Merge.java index 7931be7085..910cb8a78a 100644 --- a/h2/src/main/org/h2/command/dml/Merge.java +++ b/h2/src/main/org/h2/command/dml/Merge.java @@ -259,7 +259,7 @@ public String getPlanSQL(int sqlFlags) { } @Override - public void prepare() { + void doPrepare() { if (columns == null) { if (!valuesExpressionList.isEmpty() && valuesExpressionList.get(0).length == 0) { // special case where table is used as a sequence @@ -345,4 +345,5 @@ public void collectDependencies(HashSet dependencies) { query.collectDependencies(dependencies); } } + } diff --git a/h2/src/main/org/h2/command/dml/MergeUsing.java b/h2/src/main/org/h2/command/dml/MergeUsing.java index 0dab851782..033e9dd57b 100644 --- a/h2/src/main/org/h2/command/dml/MergeUsing.java +++ b/h2/src/main/org/h2/command/dml/MergeUsing.java @@ -188,7 +188,7 @@ public String getPlanSQL(int sqlFlags) { } @Override - public void prepare() { + void doPrepare() { onCondition.addFilterConditions(sourceTableFilter); onCondition.addFilterConditions(targetTableFilter); diff --git a/h2/src/main/org/h2/command/dml/Update.java b/h2/src/main/org/h2/command/dml/Update.java index 26781c9594..d18bf663d1 100644 --- a/h2/src/main/org/h2/command/dml/Update.java +++ b/h2/src/main/org/h2/command/dml/Update.java @@ -131,7 +131,7 @@ public String getPlanSQL(int sqlFlags) { } @Override - public void prepare() { + void doPrepare() { if (fromTableFilter != null) { targetTableFilter.addJoin(fromTableFilter, false, null); } diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java index 227e15a472..651bfe24fc 100644 --- a/h2/src/main/org/h2/command/query/Query.java +++ b/h2/src/main/org/h2/command/query/Query.java @@ -149,7 +149,7 @@ static final class OffsetFetch { boolean checkInit; - boolean isPrepared; + private boolean isPrepared; Query(SessionLocal session) { super(session); @@ -207,6 +207,20 @@ private ResultInterface queryWithoutCacheLazyCheck(long limit, ResultTarget targ */ public abstract void init(); + @Override + public final void prepare() { + if (!checkInit) { + throw DbException.getInternalError("not initialized"); + } + if (isPrepared) { + return; + } + doPrepare(); + isPrepared = true; + } + + abstract void doPrepare(); + /** * The the list of select expressions. * This may include invisible expressions such as order by expressions. diff --git a/h2/src/main/org/h2/command/query/Select.java b/h2/src/main/org/h2/command/query/Select.java index 5b1b730dd1..783aba6720 100644 --- a/h2/src/main/org/h2/command/query/Select.java +++ b/h2/src/main/org/h2/command/query/Select.java @@ -1158,14 +1158,7 @@ private int mergeGroupByExpressions(Database db, int index, ArrayList ex } @Override - public void prepare() { - if (isPrepared) { - // sometimes a subquery is prepared twice (CREATE TABLE AS SELECT) - return; - } - if (!checkInit) { - throw DbException.getInternalError("not initialized"); - } + void doPrepare() { if (orderList != null) { prepareOrder(orderList, expressions.size()); } @@ -1271,7 +1264,6 @@ public void prepare() { } } expressionArray = expressions.toArray(new Expression[0]); - isPrepared = true; } private void optimizeExpressionsAndPreserveAliases() { diff --git a/h2/src/main/org/h2/command/query/SelectUnion.java b/h2/src/main/org/h2/command/query/SelectUnion.java index a1388eccfe..5880bcf32a 100644 --- a/h2/src/main/org/h2/command/query/SelectUnion.java +++ b/h2/src/main/org/h2/command/query/SelectUnion.java @@ -246,15 +246,7 @@ public void init() { } @Override - public void prepare() { - if (isPrepared) { - // sometimes a subquery is prepared twice (CREATE TABLE AS SELECT) - return; - } - if (!checkInit) { - throw DbException.getInternalError("not initialized"); - } - isPrepared = true; + void doPrepare() { left.prepare(); right.prepare(); int len = left.getColumnCount(); diff --git a/h2/src/main/org/h2/command/query/TableValueConstructor.java b/h2/src/main/org/h2/command/query/TableValueConstructor.java index 82d171fa3c..4bf2338eca 100644 --- a/h2/src/main/org/h2/command/query/TableValueConstructor.java +++ b/h2/src/main/org/h2/command/query/TableValueConstructor.java @@ -168,15 +168,7 @@ public void init() { } @Override - public void prepare() { - if (isPrepared) { - // sometimes a subquery is prepared twice (CREATE TABLE AS SELECT) - return; - } - if (!checkInit) { - throw DbException.getInternalError("not initialized"); - } - isPrepared = true; + void doPrepare() { if (columnResolver == null) { createTable(); } diff --git a/h2/src/test/org/h2/test/db/TestMergeUsing.java b/h2/src/test/org/h2/test/db/TestMergeUsing.java index f0328a5a7e..4e078487a0 100644 --- a/h2/src/test/org/h2/test/db/TestMergeUsing.java +++ b/h2/src/test/org/h2/test/db/TestMergeUsing.java @@ -171,6 +171,7 @@ public void test() throws Exception { "SELECT 2 AS ID, 'Marcy22-updated2' AS NAME UNION ALL " + "SELECT X AS ID, 'Marcy'||X||'-inserted'||X AS NAME FROM SYSTEM_RANGE(3,4)", 4); + testDataChangeDeltaTable(); } /** @@ -298,4 +299,28 @@ private String getCreateTriggerSQL() { return buf.toString(); } + private void testDataChangeDeltaTable() throws SQLException { + deleteDb("mergeUsingQueries"); + try (Connection conn = getConnection("mergeUsingQueries")) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, C INTEGER) AS (VALUES (1, 2), (2, 3), (3, 4))"); + PreparedStatement prep = conn.prepareStatement("SELECT TEST.ID FROM FINAL TABLE ( " + + "MERGE INTO TEST USING ( " + + "SELECT T.ID, T.C FROM (SELECT 1, 3) T(ID, C) " + + ") T ON TEST.ID = T.ID " + + "WHEN MATCHED AND TEST.ID = 1 THEN " + + "UPDATE SET C = T.C " + + "WHEN NOT MATCHED THEN INSERT(ID, C) VALUES (T.ID, T.C) " + + ") TEST"); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals(1L, rs.getLong(1)); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1L, rs.getLong(1)); + } finally { + deleteDb("mergeUsingQueries"); + } + } + } From 69df6d892477b06831b5623d7e4ada01a3eca2a5 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 23 Jan 2022 10:51:19 +0800 Subject: [PATCH 09/93] Fix compatibility datetime functions in subqueries --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/command/query/Query.java | 1 + h2/src/main/org/h2/table/TableView.java | 2 +- .../org/h2/test/scripts/compatibility/compatibility.sql | 6 ++++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 1275aa0ff6..442c46030d 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 #3394: SYSDATE Considered Identifier when used in inner select +
  • Issue #3391: Hang on merge statement with data change delta table
  • PR #3384: Remove abandoned Java to C converter and fix some warnings from Sonatype Lift diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java index 651bfe24fc..c2030104df 100644 --- a/h2/src/main/org/h2/command/query/Query.java +++ b/h2/src/main/org/h2/command/query/Query.java @@ -997,6 +997,7 @@ public Table toTable(String alias, Column[] columnTemplates, ArrayList ok +SET MODE Oracle; +> ok + +SELECT (SELECT * FROM (SELECT SYSDATE)) IS NOT NULL; +>> TRUE + SET MODE Regular; > ok From 38ef4aca5a33e9cb536f391f8edf6ea038a323bd Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 23 Jan 2022 14:04:26 +0800 Subject: [PATCH 10/93] Fix SYSDATE and SYSTIMESTAMP --- h2/src/docsrc/html/changelog.html | 2 + h2/src/docsrc/html/features.html | 3 + h2/src/main/org/h2/command/Parser.java | 33 ++++--- .../main/org/h2/command/ddl/CreateView.java | 2 +- .../org/h2/expression/ExpressionColumn.java | 14 +-- .../CompatibilityDateTimeValueFunction.java | 99 +++++++++++++++++++ h2/src/main/org/h2/mode/ModeFunction.java | 38 +++++++ h2/src/main/org/h2/res/help.csv | 6 +- h2/src/main/org/h2/table/TableView.java | 15 +-- h2/src/test/org/h2/test/db/TestFunctions.java | 70 +++++++++++++ 10 files changed, 252 insertions(+), 30 deletions(-) create mode 100644 h2/src/main/org/h2/mode/CompatibilityDateTimeValueFunction.java diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 442c46030d..41d2865820 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 #3387: SYSDATE behavior changed in 2.x +
    • Issue #3394: SYSDATE Considered Identifier when used in inner select
    • Issue #3391: Hang on merge statement with data change delta table diff --git a/h2/src/docsrc/html/features.html b/h2/src/docsrc/html/features.html index 5d1f7c7f22..8dee94ff99 100644 --- a/h2/src/docsrc/html/features.html +++ b/h2/src/docsrc/html/features.html @@ -884,6 +884,7 @@

      LEGACY Compatibility Mode

      will create an UNIQUE constraint on them automatically.
    • Unsafe comparison operators between numeric and boolean values are allowed.
    • IDENTITY() and SCOPE_IDENTITY() are supported, but both are implemented like SCOPE_IDENTITY() +
    • SYSDATE, SYSTIMESTAMP, and TODAY are supported.

    DB2 Compatibility Mode

    @@ -929,6 +930,7 @@

    HSQLDB Compatibility Mode

  • LIMIT / OFFSET clauses are supported.
  • MINUS can be used instead of EXCEPT.
  • Unsafe comparison operators between numeric and boolean values are allowed. +
  • SYSDATE and TODAY are supported.

MS SQL Server Compatibility Mode

@@ -1058,6 +1060,7 @@

Oracle Compatibility Mode

  • SEQUENCE.NEXTVAL and SEQUENCE.CURRVAL are supported and return values with DECIMAL/NUMERIC data type.
  • Merge when matched clause may have WHERE clause.
  • MINUS can be used instead of EXCEPT. +
  • SYSDATE and SYSTIMESTAMP are supported.
  • PostgreSQL Compatibility Mode

    diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 505f24844a..60c0f909dc 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -3957,16 +3957,27 @@ private Expression readCompatibilityFunction(String name) { return new CurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_CATALOG); // CURRENT_DATE case "CURDATE": - case "SYSDATE": - case "TODAY": return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_DATE, true, name); + case "TODAY": + read(CLOSE_PAREN); + return ModeFunction.getCompatibilityDateTimeValueFunction(database, "TODAY", -1); // CURRENT_SCHEMA case "SCHEMA": read(CLOSE_PAREN); return new CurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_SCHEMA); // CURRENT_TIMESTAMP - case "SYSTIMESTAMP": - return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIMESTAMP, true, name); + case "SYSTIMESTAMP": { + int scale = -1; + if (!readIf(CLOSE_PAREN)) { + scale = readInt(); + if (scale < 0 || scale > ValueTime.MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), "0", + /* compile-time constant */ "" + ValueTime.MAXIMUM_SCALE); + } + read(CLOSE_PAREN); + } + return ModeFunction.getCompatibilityDateTimeValueFunction(database, "SYSTIMESTAMP", scale); + } // EXTRACT case "DAY": case "DAY_OF_MONTH": @@ -4007,12 +4018,12 @@ private Expression readCompatibilityFunction(String name) { // LOCALTIME case "CURTIME": return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIME, true, "CURTIME"); - case "SYSTIME": - read(CLOSE_PAREN); - return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIME, false, "SYSTIME"); // LOCALTIMESTAMP case "NOW": return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIMESTAMP, true, "NOW"); + case "SYSDATE": + read(CLOSE_PAREN); + return ModeFunction.getCompatibilityDateTimeValueFunction(database, "SYSDATE", -1); // LOCATE case "INSTR": { Expression arg1 = readExpression(); @@ -7464,8 +7475,8 @@ private TableView createCTEView(String cteViewName, String querySQL, synchronized (session) { view = new TableView(schema, id, cteViewName, querySQL, parameters, columnTemplateArray, session, - allowRecursiveQueryDetection, false /* literalsChecked */, true /* isTableExpression */, - isTemporary); + allowRecursiveQueryDetection, false, true, + isTemporary, false); if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) { if (!isTemporary) { database.addSchemaObject(session, view); @@ -7476,8 +7487,8 @@ private TableView createCTEView(String cteViewName, String querySQL, } view = new TableView(schema, id, cteViewName, querySQL, parameters, columnTemplateArray, session, - false/* assume recursive */, false /* literalsChecked */, true /* isTableExpression */, - isTemporary); + false/* assume recursive */, false, true, + isTemporary, false); } // both removeSchemaObject and removeLocalTempTable hold meta locks database.unlockMeta(session); diff --git a/h2/src/main/org/h2/command/ddl/CreateView.java b/h2/src/main/org/h2/command/ddl/CreateView.java index dc397ae3da..e3f5205fdb 100644 --- a/h2/src/main/org/h2/command/ddl/CreateView.java +++ b/h2/src/main/org/h2/command/ddl/CreateView.java @@ -121,7 +121,7 @@ long update(Schema schema) { false/*isTemporary*/, db); } else { view = new TableView(schema, id, viewName, querySQL, null, columnTemplatesAsUnknowns, session, - false/* allow recursive */, false/* literalsChecked */, isTableExpression, false/*temporary*/); + false, false, isTableExpression, false, false); } } else { // TODO support isTableExpression in replace function... diff --git a/h2/src/main/org/h2/expression/ExpressionColumn.java b/h2/src/main/org/h2/expression/ExpressionColumn.java index 6a207b29cf..5bc7a7d116 100644 --- a/h2/src/main/org/h2/expression/ExpressionColumn.java +++ b/h2/src/main/org/h2/expression/ExpressionColumn.java @@ -13,9 +13,9 @@ import org.h2.engine.SessionLocal; import org.h2.expression.analysis.DataAnalysisOperation; import org.h2.expression.condition.Comparison; -import org.h2.expression.function.CurrentDateTimeValueFunction; import org.h2.index.IndexCondition; import org.h2.message.DbException; +import org.h2.mode.ModeFunction; import org.h2.schema.Constant; import org.h2.schema.Schema; import org.h2.table.Column; @@ -217,14 +217,10 @@ public Expression optimize(SessionLocal session) { private Expression optimizeOther() { if (tableAlias == null && !quotedName) { - switch (StringUtils.toUpperEnglish(columnName)) { - case "SYSDATE": - case "TODAY": - return new CurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_DATE, -1); - case "SYSTIME": - return new CurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIME, -1); - case "SYSTIMESTAMP": - return new CurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIMESTAMP, -1); + Expression e = ModeFunction.getCompatibilityDateTimeValueFunction(database, + StringUtils.toUpperEnglish(columnName), -1); + if (e != null) { + return e; } } throw getColumnException(ErrorCode.COLUMN_NOT_FOUND_1); diff --git a/h2/src/main/org/h2/mode/CompatibilityDateTimeValueFunction.java b/h2/src/main/org/h2/mode/CompatibilityDateTimeValueFunction.java new file mode 100644 index 0000000000..54a0f29570 --- /dev/null +++ b/h2/src/main/org/h2/mode/CompatibilityDateTimeValueFunction.java @@ -0,0 +1,99 @@ +/* + * 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.mode; + +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Operation0; +import org.h2.expression.function.NamedExpression; +import org.h2.util.DateTimeUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Current datetime value function. + */ +final class CompatibilityDateTimeValueFunction extends Operation0 implements NamedExpression { + + /** + * The function "SYSDATE" + */ + static final int SYSDATE = 0; + + /** + * The function "SYSTIMESTAMP" + */ + static final int SYSTIMESTAMP = 1; + + private static final int[] TYPES = { Value.TIMESTAMP, Value.TIMESTAMP_TZ }; + + private static final String[] NAMES = { "SYSDATE", "SYSTIMESTAMP" }; + + private final int function, scale; + + private final TypeInfo type; + + CompatibilityDateTimeValueFunction(int function, int scale) { + this.function = function; + this.scale = scale; + if (scale < 0) { + scale = function == SYSDATE ? 0 : ValueTimestamp.DEFAULT_SCALE; + } + type = TypeInfo.getTypeInfo(TYPES[function], 0L, scale, null); + } + + @Override + public Value getValue(SessionLocal session) { + ValueTimestampTimeZone v = session.currentTimestamp(); + long dateValue = v.getDateValue(); + long timeNanos = v.getTimeNanos(); + int offsetSeconds = v.getTimeZoneOffsetSeconds(); + int newOffset = TimeZoneProvider.getDefault() + .getTimeZoneOffsetUTC(DateTimeUtils.getEpochSeconds(dateValue, timeNanos, offsetSeconds)); + if (offsetSeconds != newOffset) { + v = DateTimeUtils.timestampTimeZoneAtOffset(dateValue, timeNanos, offsetSeconds, newOffset); + } + return (function == SYSDATE ? ValueTimestamp.fromDateValueAndNanos(v.getDateValue(), v.getTimeNanos()) : v) + .castTo(type, session); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(getName()); + if (scale >= 0) { + builder.append('(').append(scale).append(')'); + } + return builder; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return true; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public int getCost() { + return 1; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/mode/ModeFunction.java b/h2/src/main/org/h2/mode/ModeFunction.java index 59f212242e..ba75a40409 100644 --- a/h2/src/main/org/h2/mode/ModeFunction.java +++ b/h2/src/main/org/h2/mode/ModeFunction.java @@ -11,6 +11,7 @@ import org.h2.engine.SessionLocal; import org.h2.expression.Expression; import org.h2.expression.ExpressionVisitor; +import org.h2.expression.function.CurrentDateTimeValueFunction; import org.h2.expression.function.FunctionN; import org.h2.message.DbException; import org.h2.value.Value; @@ -67,6 +68,43 @@ private static ModeFunction getCompatibilityModeFunction(String name, ModeEnum m } } + /** + * Get an instance of the given function without parentheses for this + * database. If no function with this name is found, null is returned. + * + * @param database the database + * @param name the upper case function name + * @param scale the scale, or {@code -1} + * @return the function object or null + */ + @SuppressWarnings("incomplete-switch") + public static Expression getCompatibilityDateTimeValueFunction(Database database, String name, int scale) { + switch (name) { + case "SYSDATE": + switch (database.getMode().getEnum()) { + case LEGACY: + case HSQLDB: + case Oracle: + return new CompatibilityDateTimeValueFunction(CompatibilityDateTimeValueFunction.SYSDATE, -1); + } + break; + case "SYSTIMESTAMP": + switch (database.getMode().getEnum()) { + case LEGACY: + case Oracle: + return new CompatibilityDateTimeValueFunction(CompatibilityDateTimeValueFunction.SYSTIMESTAMP, scale); + } + break; + case "TODAY": + switch (database.getMode().getEnum()) { + case LEGACY: + case HSQLDB: + return new CurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_DATE, scale); + } + break; + } + return null; + } /** * Creates a new instance of function. diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index d783fa770a..b5ef3008b5 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -5680,7 +5680,7 @@ CALL TRANSLATE('Hello world', 'eo', 'EO') " "Functions (Time and Date)","CURRENT_DATE"," -CURRENT_DATE | @c@ { CURDATE() | SYSDATE | TODAY } +CURRENT_DATE "," Returns the current date. @@ -5733,7 +5733,7 @@ CURRENT_TIMESTAMP(9) " "Functions (Time and Date)","LOCALTIME"," -LOCALTIME [ (int) ] | @c@ CURTIME([ int ]) +LOCALTIME [ (int) ] "," Returns the current time without time zone. If fractional seconds precision is specified it should be from 0 to 9, 0 is default. @@ -5752,7 +5752,7 @@ LOCALTIME(9) " "Functions (Time and Date)","LOCALTIMESTAMP"," -LOCALTIMESTAMP [ (int) ] | @c@ NOW( [ int ] ) +LOCALTIMESTAMP [ (int) ] "," Returns the current timestamp without time zone. If fractional seconds precision is specified it should be from 0 to 9, 6 is default. diff --git a/h2/src/main/org/h2/table/TableView.java b/h2/src/main/org/h2/table/TableView.java index 7f15eb344c..b6525bf915 100644 --- a/h2/src/main/org/h2/table/TableView.java +++ b/h2/src/main/org/h2/table/TableView.java @@ -59,12 +59,15 @@ public class TableView extends Table { private ResultInterface recursiveResult; private boolean isRecursiveQueryDetected; private boolean isTableExpression; + private boolean isSubquery; public TableView(Schema schema, int id, String name, String querySQL, ArrayList params, Column[] columnTemplates, SessionLocal session, - boolean allowRecursive, boolean literalsChecked, boolean isTableExpression, boolean isTemporary) { + boolean allowRecursive, boolean literalsChecked, boolean isTableExpression, boolean isTemporary, + boolean isSubquery) { super(schema, id, name, false, true); setTemporary(isTemporary); + this.isSubquery = isSubquery; init(querySQL, params, columnTemplates, session, allowRecursive, literalsChecked, isTableExpression); } @@ -177,7 +180,7 @@ private void initColumnsAndTables(SessionLocal session, boolean literalsChecked) type = columnTemplates[i].getType(); } if (name == null) { - name = expr.getColumnNameForView(session, i); + name = isSubquery ? expr.getAlias(session, i) : expr.getColumnNameForView(session, i); } if (type.getValueType() == Value.UNKNOWN) { type = expr.getType(); @@ -504,8 +507,8 @@ public static TableView createTempView(SessionLocal session, User owner, String querySQL = query.getPlanSQL(DEFAULT_SQL_FLAGS); TableView v = new TableView(mainSchema, 0, name, querySQL, query.getParameters(), columnTemplates, session, - false/* allow recursive */, true /* literals have already been checked when parsing original query */, - false /* is table expression */, true/*temporary*/); + false, true /* literals have already been checked when parsing original query */, + false, true, true); if (v.createException != null) { throw v.createException; } @@ -700,7 +703,7 @@ public static TableView createTableViewMaybeRecursive(Schema schema, int id, Str // build with recursion turned on TableView view = new TableView(schema, id, name, querySQL, parameters, columnTemplateList.toArray(columnTemplates), session, - true/* try recursive */, literalsChecked, isTableExpression, isTemporary); + true/* try recursive */, literalsChecked, isTableExpression, isTemporary, false); // is recursion really detected ? if not - recreate it without recursion flag // and no recursive index @@ -719,7 +722,7 @@ public static TableView createTableViewMaybeRecursive(Schema schema, int id, Str } view = new TableView(schema, id, name, querySQL, parameters, columnTemplates, session, - false/* detected not recursive */, literalsChecked, isTableExpression, isTemporary); + false/* detected not recursive */, literalsChecked, isTableExpression, isTemporary, false); } return view; diff --git a/h2/src/test/org/h2/test/db/TestFunctions.java b/h2/src/test/org/h2/test/db/TestFunctions.java index dd601a5050..af324b458f 100644 --- a/h2/src/test/org/h2/test/db/TestFunctions.java +++ b/h2/src/test/org/h2/test/db/TestFunctions.java @@ -31,6 +31,8 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalQueries; @@ -130,6 +132,7 @@ public void test() throws Exception { testThatCurrentTimestampIsSane(); testThatCurrentTimestampStaysTheSameWithinATransaction(); testThatCurrentTimestampUpdatesOutsideATransaction(); + testCompatibilityDateTime(); testAnnotationProcessorsOutput(); testSignal(); @@ -1890,6 +1893,36 @@ private void testSignal() throws SQLException { conn.close(); } + private void testLegacyDateTime() throws SQLException { + deleteDb("functions"); + TimeZone tz = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("GMT+1")); + Connection conn = getConnection("functions;MODE=LEGACY"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9)"); + rs.next(); + LocalDateTime ltd = rs.getObject(1, LocalDateTime.class); + OffsetDateTime odt = rs.getObject(2, OffsetDateTime.class); + OffsetDateTime odt0 = rs.getObject(3, OffsetDateTime.class); + OffsetDateTime odt9 = rs.getObject(4, OffsetDateTime.class); + assertEquals(3_600, odt.getOffset().getTotalSeconds()); + assertEquals(3_600, odt9.getOffset().getTotalSeconds()); + assertEquals(ltd, odt0.toLocalDateTime()); + stat.execute("SET TIME ZONE '2:00'"); + rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9)"); + rs.next(); + assertEquals(ltd, rs.getObject(1, LocalDateTime.class)); + assertEquals(odt, rs.getObject(2, OffsetDateTime.class)); + assertEquals(odt0, rs.getObject(3, OffsetDateTime.class)); + assertEquals(odt9, rs.getObject(4, OffsetDateTime.class)); + conn.close(); + } finally { + TimeZone.setDefault(tz); + } + } + private void testThatCurrentTimestampIsSane() throws SQLException, ParseException { deleteDb("functions"); @@ -1971,6 +2004,43 @@ private void testThatCurrentTimestampUpdatesOutsideATransaction() conn.close(); } + private void testCompatibilityDateTime() throws SQLException { + deleteDb("functions"); + TimeZone tz = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("GMT+1")); + for (String mode : new String[] { "LEGACY", "ORACLE" }) { + Connection conn = getConnection("functions;MODE=" + mode); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("SET TIME ZONE '2:00'"); + ResultSet rs = stat.executeQuery( + "SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9) FROM DUAL"); + rs.next(); + LocalDateTime ltd = rs.getObject(1, LocalDateTime.class); + OffsetDateTime odt = rs.getObject(2, OffsetDateTime.class); + OffsetDateTime odt0 = rs.getObject(3, OffsetDateTime.class); + OffsetDateTime odt9 = rs.getObject(4, OffsetDateTime.class); + assertEquals(3_600, odt.getOffset().getTotalSeconds()); + assertEquals(3_600, odt9.getOffset().getTotalSeconds()); + assertEquals(ltd, odt0.toLocalDateTime()); + if (mode.equals("LEGACY")) { + stat.execute("SET TIME ZONE '3:00'"); + rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9) FROM DUAL"); + rs.next(); + assertEquals(ltd, rs.getObject(1, LocalDateTime.class)); + assertEquals(odt, rs.getObject(2, OffsetDateTime.class)); + assertEquals(odt0, rs.getObject(3, OffsetDateTime.class)); + assertEquals(odt9, rs.getObject(4, OffsetDateTime.class)); + } + conn.close(); + } + } finally { + TimeZone.setDefault(tz); + } + } + + private void testOverrideAlias() throws SQLException { deleteDb("functions"); Connection conn = getConnection("functions"); From 8b0b64ce3844ec6e75022a97499df7b0ace60a23 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Mon, 24 Jan 2022 19:04:47 +0800 Subject: [PATCH 11/93] Fix commit ec83d7a9909ccc040dc7f6c382886004611854e1 --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/command/Parser.java | 11 ++++++----- .../org/h2/test/scripts/functions/system/rownum.sql | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 41d2865820..38a7f70bcf 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 #3400: Regression: ORDER BY ROWNUM fails with General error: "Unexpected code path" +
    • Issue #3387: SYSDATE behavior changed in 2.x
    • Issue #3394: SYSDATE Considered Identifier when used in inner select diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 60c0f909dc..c05ceab58e 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -4873,7 +4873,7 @@ private SequenceValue readIfSequencePseudoColumn(String schema, String objectNam Sequence sequence = findSequence(schema, objectName); if (sequence != null) { read(); - return new SequenceValue(sequence, getCurrentPrepared()); + return new SequenceValue(sequence, getCurrentPreparedOrSelect()); } } else if (isToken("CURRVAL")) { Sequence sequence = findSequence(schema, objectName); @@ -5081,7 +5081,7 @@ private Expression readTerm() { if (currentSelect == null && currentPrepared == null) { throw getSyntaxError(); } - r = new Rownum(getCurrentPrepared()); + r = new Rownum(getCurrentPreparedOrSelect()); break; case NULL: read(); @@ -5380,7 +5380,7 @@ && equalsToken("E", name)) { if (equalsToken("NEXT", name)) { int index = tokenIndex; if (readIf(VALUE) && readIf(FOR)) { - return new SequenceValue(readSequence(), getCurrentPrepared()); + return new SequenceValue(readSequence(), getCurrentPreparedOrSelect()); } setTokenIndex(index); } @@ -5458,8 +5458,9 @@ && equalsToken("UUID", name)) { return new ExpressionColumn(database, null, null, name, quoted); } - private Prepared getCurrentPrepared() { - return currentPrepared; + private Prepared getCurrentPreparedOrSelect() { + Prepared p = currentPrepared; + return p != null ? p : currentSelect; } private Expression readInterval() { diff --git a/h2/src/test/org/h2/test/scripts/functions/system/rownum.sql b/h2/src/test/org/h2/test/scripts/functions/system/rownum.sql index 0893274095..44f26f981d 100644 --- a/h2/src/test/org/h2/test/scripts/functions/system/rownum.sql +++ b/h2/src/test/org/h2/test/scripts/functions/system/rownum.sql @@ -29,3 +29,5 @@ SELECT * FROM (VALUES 1, 2) AS T1(X), (VALUES 1, 2) AS T2(X) WHERE ROWNUM = 1; > 1 1 > rows: 1 +SELECT 1 ORDER BY ROWNUM; +>> 1 From b5203df460bca8e66e975f2715116da4f76cdf27 Mon Sep 17 00:00:00 2001 From: mans2singh Date: Fri, 28 Jan 2022 22:48:49 -0500 Subject: [PATCH 12/93] ISSUE-3406 - Fixed ClassCastException on iterating result set for the FunctionMultiReturn.polar2CartesianArray call (#3407) --- h2/src/test/org/h2/samples/FunctionMultiReturn.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/h2/src/test/org/h2/samples/FunctionMultiReturn.java b/h2/src/test/org/h2/samples/FunctionMultiReturn.java index 197341d996..8b1c60e34b 100644 --- a/h2/src/test/org/h2/samples/FunctionMultiReturn.java +++ b/h2/src/test/org/h2/samples/FunctionMultiReturn.java @@ -67,10 +67,9 @@ public static void main(String... args) throws Exception { while (rs.next()) { double r = rs.getDouble(1); double a = rs.getDouble(2); - Object o = rs.getObject(3); - Object[] xy = (Object[]) o; - double x = (Double) xy[0]; - double y = (Double) xy[1]; + Double [] xy = rs.getObject(3, Double[].class); + double x = xy[0]; + double y = xy[1]; System.out.println("(r=" + r + " a=" + a + ") :" + " (x=" + x + ", y=" + y + ")"); } From ad869b26bf71dff10bd2702e20fe3363f881d20c Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 29 Jan 2022 17:04:59 +0800 Subject: [PATCH 13/93] Don't delete target non-regular files in SCRIPT TO --- h2/src/docsrc/html/changelog.html | 4 + .../main/org/h2/command/dml/ScriptBase.java | 4 +- h2/src/main/org/h2/store/fs/FilePath.java | 7 ++ .../main/org/h2/store/fs/FilePathWrapper.java | 5 + h2/src/main/org/h2/store/fs/FileUtils.java | 10 ++ .../org/h2/store/fs/disk/FilePathDisk.java | 5 + .../main/org/h2/store/fs/mem/FilePathMem.java | 11 +++ .../h2/store/fs/niomem/FilePathNioMem.java | 12 +++ .../main/org/h2/store/fs/zip/FilePathZip.java | 15 ++- h2/src/test/org/h2/test/db/TestFunctions.java | 13 +-- .../test/org/h2/test/utils/FilePathDebug.java | 6 ++ .../org/h2/test/utils/FilePathUnstable.java | 93 ------------------- h2/src/tools/org/h2/build/doc/dictionary.txt | 2 +- h2/src/tools/org/h2/dev/fs/FilePathZip2.java | 15 ++- 14 files changed, 95 insertions(+), 107 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 38a7f70bcf..fe771e203a 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,6 +21,10 @@

      Change Log

      Next Version (unreleased)

        +
      • Issue #3405: Enhance SCRIPT to support operations on STDOUT +
      • +
      • Issue #3406 / PR #3407: FunctionMultiReturn.polar2CartesianArray result set iteration throws ClassCastException +
      • Issue #3400: Regression: ORDER BY ROWNUM fails with General error: "Unexpected code path"
      • Issue #3387: SYSDATE behavior changed in 2.x diff --git a/h2/src/main/org/h2/command/dml/ScriptBase.java b/h2/src/main/org/h2/command/dml/ScriptBase.java index e1b99c039f..8e27b45329 100644 --- a/h2/src/main/org/h2/command/dml/ScriptBase.java +++ b/h2/src/main/org/h2/command/dml/ScriptBase.java @@ -104,7 +104,9 @@ public boolean isTransactional() { void deleteStore() { String file = getFileName(); if (file != null) { - FileUtils.delete(file); + if (FileUtils.isRegularFile(file)) { + FileUtils.delete(file); + } } } diff --git a/h2/src/main/org/h2/store/fs/FilePath.java b/h2/src/main/org/h2/store/fs/FilePath.java index 1225165163..a3409cd232 100644 --- a/h2/src/main/org/h2/store/fs/FilePath.java +++ b/h2/src/main/org/h2/store/fs/FilePath.java @@ -177,6 +177,13 @@ public static void unregister(FilePath provider) { */ public abstract boolean isDirectory(); + /** + * Check if it is a regular file. + * + * @return true if it is a regular file + */ + public abstract boolean isRegularFile(); + /** * Check if the file name includes a path. * diff --git a/h2/src/main/org/h2/store/fs/FilePathWrapper.java b/h2/src/main/org/h2/store/fs/FilePathWrapper.java index f3b14c00b8..da29e92cab 100644 --- a/h2/src/main/org/h2/store/fs/FilePathWrapper.java +++ b/h2/src/main/org/h2/store/fs/FilePathWrapper.java @@ -108,6 +108,11 @@ public boolean isDirectory() { return base.isDirectory(); } + @Override + public boolean isRegularFile() { + return base.isRegularFile(); + } + @Override public long lastModified() { return base.lastModified(); diff --git a/h2/src/main/org/h2/store/fs/FileUtils.java b/h2/src/main/org/h2/store/fs/FileUtils.java index 276114d780..32fcdfee57 100644 --- a/h2/src/main/org/h2/store/fs/FileUtils.java +++ b/h2/src/main/org/h2/store/fs/FileUtils.java @@ -239,6 +239,16 @@ public static boolean isDirectory(String fileName) { return FilePath.get(fileName).isDirectory(); } + /** + * Tests whether a file is a regular file. + * + * @param fileName the file or directory name + * @return true if it is a regular file + */ + public static boolean isRegularFile(String fileName) { + return FilePath.get(fileName).isRegularFile(); + } + /** * Open a random access file object. * This method is similar to Java 7 diff --git a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java index ba3395f694..9c30c40cdd 100644 --- a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java +++ b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java @@ -303,6 +303,11 @@ public boolean isDirectory() { return Files.isDirectory(Paths.get(name)); } + @Override + public boolean isRegularFile() { + return Files.isRegularFile(Paths.get(name)); + } + @Override public boolean isAbsolute() { return Paths.get(name).isAbsolute(); diff --git a/h2/src/main/org/h2/store/fs/mem/FilePathMem.java b/h2/src/main/org/h2/store/fs/mem/FilePathMem.java index 502f321f14..37290bfa82 100644 --- a/h2/src/main/org/h2/store/fs/mem/FilePathMem.java +++ b/h2/src/main/org/h2/store/fs/mem/FilePathMem.java @@ -127,6 +127,17 @@ public boolean isDirectory() { } } + @Override + public boolean isRegularFile() { + if (isRoot()) { + return false; + } + synchronized (MEMORY_FILES) { + FileMemData d = MEMORY_FILES.get(name); + return d != null && d != DIRECTORY; + } + } + @Override public boolean isAbsolute() { // TODO relative files are not supported diff --git a/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java b/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java index ed23c6fb9f..c9cb0cb622 100644 --- a/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java +++ b/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java @@ -127,6 +127,18 @@ public boolean isDirectory() { } } + @Override + public boolean isRegularFile() { + if (isRoot()) { + return false; + } + // TODO in memory file system currently + // does not really support directories + synchronized (MEMORY_FILES) { + return MEMORY_FILES.get(name) != null; + } + } + @Override public boolean isAbsolute() { // TODO relative files are not supported diff --git a/h2/src/main/org/h2/store/fs/zip/FilePathZip.java b/h2/src/main/org/h2/store/fs/zip/FilePathZip.java index 7262fd5e49..5cacabdf55 100644 --- a/h2/src/main/org/h2/store/fs/zip/FilePathZip.java +++ b/h2/src/main/org/h2/store/fs/zip/FilePathZip.java @@ -84,10 +84,19 @@ public FilePath unwrap() { @Override public boolean isDirectory() { + return isRegularOrDirectory(true); + } + + @Override + public boolean isRegularFile() { + return isRegularOrDirectory(false); + } + + private boolean isRegularOrDirectory(boolean directory) { try { String entryName = getEntryName(); if (entryName.isEmpty()) { - return true; + return directory; } try (ZipFile file = openZipFile()) { Enumeration en = file.entries(); @@ -95,11 +104,11 @@ public boolean isDirectory() { ZipEntry entry = en.nextElement(); String n = entry.getName(); if (n.equals(entryName)) { - return entry.isDirectory(); + return entry.isDirectory() == directory; } else if (n.startsWith(entryName)) { if (n.length() == entryName.length() + 1) { if (n.equals(entryName + "/")) { - return true; + return directory; } } } diff --git a/h2/src/test/org/h2/test/db/TestFunctions.java b/h2/src/test/org/h2/test/db/TestFunctions.java index af324b458f..cb2521a71e 100644 --- a/h2/src/test/org/h2/test/db/TestFunctions.java +++ b/h2/src/test/org/h2/test/db/TestFunctions.java @@ -135,6 +135,7 @@ public void test() throws Exception { testCompatibilityDateTime(); testAnnotationProcessorsOutput(); testSignal(); + testLegacyDateTime(); deleteDb("functions"); } @@ -1903,17 +1904,17 @@ private void testLegacyDateTime() throws SQLException { Statement stat = conn.createStatement(); ResultSet rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9)"); rs.next(); - LocalDateTime ltd = rs.getObject(1, LocalDateTime.class); + LocalDateTime ldt = rs.getObject(1, LocalDateTime.class); OffsetDateTime odt = rs.getObject(2, OffsetDateTime.class); OffsetDateTime odt0 = rs.getObject(3, OffsetDateTime.class); OffsetDateTime odt9 = rs.getObject(4, OffsetDateTime.class); assertEquals(3_600, odt.getOffset().getTotalSeconds()); assertEquals(3_600, odt9.getOffset().getTotalSeconds()); - assertEquals(ltd, odt0.toLocalDateTime()); + assertEquals(ldt, odt0.toLocalDateTime()); stat.execute("SET TIME ZONE '2:00'"); rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9)"); rs.next(); - assertEquals(ltd, rs.getObject(1, LocalDateTime.class)); + assertEquals(ldt, rs.getObject(1, LocalDateTime.class)); assertEquals(odt, rs.getObject(2, OffsetDateTime.class)); assertEquals(odt0, rs.getObject(3, OffsetDateTime.class)); assertEquals(odt9, rs.getObject(4, OffsetDateTime.class)); @@ -2017,18 +2018,18 @@ private void testCompatibilityDateTime() throws SQLException { ResultSet rs = stat.executeQuery( "SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9) FROM DUAL"); rs.next(); - LocalDateTime ltd = rs.getObject(1, LocalDateTime.class); + LocalDateTime ldt = rs.getObject(1, LocalDateTime.class); OffsetDateTime odt = rs.getObject(2, OffsetDateTime.class); OffsetDateTime odt0 = rs.getObject(3, OffsetDateTime.class); OffsetDateTime odt9 = rs.getObject(4, OffsetDateTime.class); assertEquals(3_600, odt.getOffset().getTotalSeconds()); assertEquals(3_600, odt9.getOffset().getTotalSeconds()); - assertEquals(ltd, odt0.toLocalDateTime()); + assertEquals(ldt, odt0.toLocalDateTime()); if (mode.equals("LEGACY")) { stat.execute("SET TIME ZONE '3:00'"); rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9) FROM DUAL"); rs.next(); - assertEquals(ltd, rs.getObject(1, LocalDateTime.class)); + assertEquals(ldt, rs.getObject(1, LocalDateTime.class)); assertEquals(odt, rs.getObject(2, OffsetDateTime.class)); assertEquals(odt0, rs.getObject(3, OffsetDateTime.class)); assertEquals(odt9, rs.getObject(4, OffsetDateTime.class)); diff --git a/h2/src/test/org/h2/test/utils/FilePathDebug.java b/h2/src/test/org/h2/test/utils/FilePathDebug.java index 13144377a0..90e1a577b3 100644 --- a/h2/src/test/org/h2/test/utils/FilePathDebug.java +++ b/h2/src/test/org/h2/test/utils/FilePathDebug.java @@ -113,6 +113,12 @@ public boolean isDirectory() { return super.isDirectory(); } + @Override + public boolean isRegularFile() { + trace(name, "isRegularFile"); + return super.isRegularFile(); + } + @Override public boolean canWrite() { trace(name, "canWrite"); diff --git a/h2/src/test/org/h2/test/utils/FilePathUnstable.java b/h2/src/test/org/h2/test/utils/FilePathUnstable.java index 6343bf5ab6..2a40401d09 100644 --- a/h2/src/test/org/h2/test/utils/FilePathUnstable.java +++ b/h2/src/test/org/h2/test/utils/FilePathUnstable.java @@ -6,12 +6,9 @@ package org.h2.test.utils; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; -import java.util.List; import java.util.Random; import org.h2.store.fs.FileBase; @@ -107,101 +104,11 @@ void checkError() throws IOException { } } - @Override - public void createDirectory() { - super.createDirectory(); - } - - @Override - public boolean createFile() { - return super.createFile(); - } - - @Override - public void delete() { - super.delete(); - } - - @Override - public boolean exists() { - return super.exists(); - } - - @Override - public String getName() { - return super.getName(); - } - - @Override - public long lastModified() { - return super.lastModified(); - } - - @Override - public FilePath getParent() { - return super.getParent(); - } - - @Override - public boolean isAbsolute() { - return super.isAbsolute(); - } - - @Override - public boolean isDirectory() { - return super.isDirectory(); - } - - @Override - public boolean canWrite() { - return super.canWrite(); - } - - @Override - public boolean setReadOnly() { - return super.setReadOnly(); - } - - @Override - public long size() { - return super.size(); - } - - @Override - public List newDirectoryStream() { - return super.newDirectoryStream(); - } - - @Override - public FilePath toRealPath() { - return super.toRealPath(); - } - - @Override - public InputStream newInputStream() throws IOException { - return super.newInputStream(); - } - @Override public FileChannel open(String mode) throws IOException { return new FileUnstable(this, super.open(mode)); } - @Override - public OutputStream newOutputStream(boolean append) throws IOException { - return super.newOutputStream(append); - } - - @Override - public void moveTo(FilePath newName, boolean atomicReplace) { - super.moveTo(newName, atomicReplace); - } - - @Override - public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { - return super.createTempFile(suffix, inTempDir); - } - @Override public String getScheme() { return "unstable"; diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt index 9e48dba39b..289fe62130 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 supplementary happier hasn officially rnrn +accidental wbr subtree recognising supplementary happier hasn officially rnrn sonatype abandoned ldt odt diff --git a/h2/src/tools/org/h2/dev/fs/FilePathZip2.java b/h2/src/tools/org/h2/dev/fs/FilePathZip2.java index 92578827e0..9b0acae248 100644 --- a/h2/src/tools/org/h2/dev/fs/FilePathZip2.java +++ b/h2/src/tools/org/h2/dev/fs/FilePathZip2.java @@ -124,10 +124,19 @@ public FilePath unwrap() { @Override public boolean isDirectory() { + return isRegularOrDirectory(true); + } + + @Override + public boolean isRegularFile() { + return isRegularOrDirectory(false); + } + + private boolean isRegularOrDirectory(boolean directory) { try { String entryName = getEntryName(); if (entryName.length() == 0) { - return true; + return directory; } ZipInputStream file = openZip(); boolean result = false; @@ -138,12 +147,12 @@ public boolean isDirectory() { } String n = entry.getName(); if (n.equals(entryName)) { - result = entry.isDirectory(); + result = entry.isDirectory() == directory; break; } else if (n.startsWith(entryName)) { if (n.length() == entryName.length() + 1) { if (n.equals(entryName + "/")) { - result = true; + result = directory; break; } } From 3fd43e46d3fb4daa3a270c7e7458a436a8074ebd Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 30 Jan 2022 17:32:22 +0800 Subject: [PATCH 14/93] Fix OOME with nested derived tables --- h2/src/docsrc/html/changelog.html | 2 + h2/src/main/org/h2/command/Parser.java | 25 +- .../h2/command/ddl/AlterTableAlterColumn.java | 2 +- .../main/org/h2/command/ddl/CreateView.java | 2 +- h2/src/main/org/h2/command/query/Query.java | 17 +- h2/src/main/org/h2/command/query/Select.java | 41 ++- .../org/h2/command/query/SelectUnion.java | 13 +- .../command/query/TableValueConstructor.java | 7 +- h2/src/main/org/h2/engine/SessionLocal.java | 55 ++- h2/src/main/org/h2/engine/User.java | 12 +- ...Cursor.java => QueryExpressionCursor.java} | 9 +- ...ewIndex.java => QueryExpressionIndex.java} | 57 ++- h2/src/main/org/h2/table/DerivedTable.java | 94 +++++ .../org/h2/table/InformationSchemaTable.java | 2 +- .../org/h2/table/QueryExpressionTable.java | 319 ++++++++++++++++ h2/src/main/org/h2/table/TableView.java | 339 +----------------- .../org/h2/test/scripts/queries/select.sql | 27 ++ 17 files changed, 599 insertions(+), 424 deletions(-) rename h2/src/main/org/h2/index/{ViewCursor.java => QueryExpressionCursor.java} (88%) rename h2/src/main/org/h2/index/{ViewIndex.java => QueryExpressionIndex.java} (89%) create mode 100644 h2/src/main/org/h2/table/DerivedTable.java create mode 100644 h2/src/main/org/h2/table/QueryExpressionTable.java diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index fe771e203a..4031841c20 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 #3410: OOME with nested derived tables +
        • Issue #3405: Enhance SCRIPT to support operations on STDOUT
        • Issue #3406 / PR #3407: FunctionMultiReturn.polar2CartesianArray result set iteration throws ClassCastException diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index c05ceab58e..1a57a8d30b 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -366,6 +366,7 @@ import org.h2.table.FunctionTable; import org.h2.table.IndexColumn; import org.h2.table.IndexHints; +import org.h2.table.QueryExpressionTable; import org.h2.table.RangeTable; import org.h2.table.Table; import org.h2.table.TableFilter; @@ -540,6 +541,22 @@ public Prepared prepare(String sql) { return p; } + /** + * Parse a query and prepare its expressions. Rights and literals must be + * already checked. + * + * @param sql the SQL statement to parse + * @return the prepared object + */ + public Query prepareQueryExpression(String sql) { + Query q = (Query) parse(sql, null); + q.prepareExpressions(); + if (currentTokenType != END_OF_INPUT) { + throw getSyntaxError(); + } + return q; + } + /** * Parse a statement or a list of statements, and prepare it for execution. * @@ -1912,7 +1929,7 @@ private TableFilter readDerivedTableWithCorrelation() { Column[] columnTemplates = null; if (derivedColumnNames != null) { query.init(); - columnTemplates = TableView.createQueryColumnTemplateList( + columnTemplates = QueryExpressionTable.createQueryColumnTemplateList( derivedColumnNames.toArray(new String[0]), query, new String[1]) .toArray(new Column[0]); } @@ -7449,7 +7466,7 @@ private TableView parseSingleCommonTableExpression(boolean isTemporary) { withQuery.session = session; } read(CLOSE_PAREN); - columnTemplateList = TableView.createQueryColumnTemplateList(cols, withQuery, querySQLOutput); + columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery, querySQLOutput); } finally { TableView.destroyShadowTableForRecursiveExpression(isTemporary, session, recursiveTable); @@ -7477,7 +7494,7 @@ private TableView createCTEView(String cteViewName, String querySQL, view = new TableView(schema, id, cteViewName, querySQL, parameters, columnTemplateArray, session, allowRecursiveQueryDetection, false, true, - isTemporary, false); + isTemporary); if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) { if (!isTemporary) { database.addSchemaObject(session, view); @@ -7489,7 +7506,7 @@ private TableView createCTEView(String cteViewName, String querySQL, view = new TableView(schema, id, cteViewName, querySQL, parameters, columnTemplateArray, session, false/* assume recursive */, false, true, - isTemporary, false); + isTemporary); } // both removeSchemaObject and removeLocalTempTable hold meta locks database.unlockMeta(session); diff --git a/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java b/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java index ebb8baa2ef..2f21bae787 100644 --- a/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java +++ b/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java @@ -625,7 +625,7 @@ private void checkViews(SchemaObject sourceTable, SchemaObject newTable) { private void checkViewsAreValid(DbObject tableOrView) { for (DbObject view : tableOrView.getChildren()) { if (view instanceof TableView) { - String sql = ((TableView) view).getQuery(); + String sql = ((TableView) view).getQuerySQL(); // check if the query is still valid // do not execute, not even with limit 1, because that could // have side effects or take a very long time diff --git a/h2/src/main/org/h2/command/ddl/CreateView.java b/h2/src/main/org/h2/command/ddl/CreateView.java index e3f5205fdb..4134afccf1 100644 --- a/h2/src/main/org/h2/command/ddl/CreateView.java +++ b/h2/src/main/org/h2/command/ddl/CreateView.java @@ -121,7 +121,7 @@ long update(Schema schema) { false/*isTemporary*/, db); } else { view = new TableView(schema, id, viewName, querySQL, null, columnTemplatesAsUnknowns, session, - false, false, isTableExpression, false, false); + false, false, isTableExpression, false); } } else { // TODO support isTableExpression in replace function... diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java index c2030104df..697575c8b3 100644 --- a/h2/src/main/org/h2/command/query/Query.java +++ b/h2/src/main/org/h2/command/query/Query.java @@ -31,9 +31,9 @@ import org.h2.result.SortOrder; import org.h2.table.Column; import org.h2.table.ColumnResolver; +import org.h2.table.DerivedTable; import org.h2.table.Table; import org.h2.table.TableFilter; -import org.h2.table.TableView; import org.h2.util.Utils; import org.h2.value.ExtTypeInfoRow; import org.h2.value.TypeInfo; @@ -149,7 +149,7 @@ static final class OffsetFetch { boolean checkInit; - private boolean isPrepared; + boolean isPrepared; Query(SessionLocal session) { super(session); @@ -215,11 +215,13 @@ public final void prepare() { if (isPrepared) { return; } - doPrepare(); - isPrepared = true; + prepareExpressions(); + preparePlan(); } - abstract void doPrepare(); + public abstract void prepareExpressions(); + + public abstract void preparePlan(); /** * The the list of select expressions. @@ -997,9 +999,8 @@ public Table toTable(String alias, Column[] columnTemplates, ArrayList { if (f != top && f.getTable().getTableType() == TableType.VIEW) { - ViewIndex idx = (ViewIndex) f.getIndex(); + QueryExpressionIndex idx = (QueryExpressionIndex) f.getIndex(); if (idx != null && idx.getQuery() != null) { idx.getQuery().setNeverLazy(true); } @@ -1158,7 +1158,7 @@ private int mergeGroupByExpressions(Database db, int index, ArrayList ex } @Override - void doPrepare() { + public void prepareExpressions() { if (orderList != null) { prepareOrder(orderList, expressions.size()); } @@ -1175,25 +1175,30 @@ void doPrepare() { } if (condition != null) { condition = condition.optimizeCondition(session); - if (condition != null) { - for (TableFilter f : filters) { - // outer joins: must not add index conditions such as - // "c is null" - example: - // create table parent(p int primary key) as select 1; - // create table child(c int primary key, pc int); - // insert into child values(2, 1); - // select p, c from parent - // left outer join child on p = pc where c is null; - if (!f.isJoinOuter() && !f.isJoinOuterIndirect()) { - condition.createIndexConditions(session, f); - } - } - } } if (isGroupQuery && groupIndex == null && havingIndex < 0 && qualifyIndex < 0 && condition == null && filters.size() == 1) { isQuickAggregateQuery = isEverything(ExpressionVisitor.getOptimizableVisitor(filters.get(0).getTable())); } + expressionArray = expressions.toArray(new Expression[0]); + } + + @Override + public void preparePlan() { + if (condition != null) { + for (TableFilter f : filters) { + // outer joins: must not add index conditions such as + // "c is null" - example: + // create table parent(p int primary key) as select 1; + // create table child(c int primary key, pc int); + // insert into child values(2, 1); + // select p, c from parent + // left outer join child on p = pc where c is null; + if (!f.isJoinOuter() && !f.isJoinOuterIndirect()) { + condition.createIndexConditions(session, f); + } + } + } cost = preparePlan(session.isParsingCreateView()); if (distinct && session.getDatabase().getSettings().optimizeDistinct && !isGroupQuery && filters.size() == 1 && @@ -1263,7 +1268,7 @@ void doPrepare() { } } } - expressionArray = expressions.toArray(new Expression[0]); + isPrepared = true; } private void optimizeExpressionsAndPreserveAliases() { diff --git a/h2/src/main/org/h2/command/query/SelectUnion.java b/h2/src/main/org/h2/command/query/SelectUnion.java index 5880bcf32a..978a22585c 100644 --- a/h2/src/main/org/h2/command/query/SelectUnion.java +++ b/h2/src/main/org/h2/command/query/SelectUnion.java @@ -246,9 +246,9 @@ public void init() { } @Override - void doPrepare() { - left.prepare(); - right.prepare(); + public void prepareExpressions() { + left.prepareExpressions(); + right.prepareExpressions(); int len = left.getColumnCount(); // set the correct expressions now expressions = new ArrayList<>(len); @@ -271,6 +271,13 @@ void doPrepare() { expressionArray = expressions.toArray(new Expression[0]); } + @Override + public void preparePlan() { + left.preparePlan(); + right.preparePlan(); + isPrepared = true; + } + @Override public double getCost() { return left.getCost() + right.getCost(); diff --git a/h2/src/main/org/h2/command/query/TableValueConstructor.java b/h2/src/main/org/h2/command/query/TableValueConstructor.java index 4bf2338eca..713b154d86 100644 --- a/h2/src/main/org/h2/command/query/TableValueConstructor.java +++ b/h2/src/main/org/h2/command/query/TableValueConstructor.java @@ -168,7 +168,7 @@ public void init() { } @Override - void doPrepare() { + public void prepareExpressions() { if (columnResolver == null) { createTable(); } @@ -192,6 +192,10 @@ void doPrepare() { cleanupOrder(); } expressionArray = expressions.toArray(new Expression[0]); + } + + @Override + public void preparePlan() { double cost = 0; int columnCount = visibleColumnCount; for (ArrayList r : rows) { @@ -200,6 +204,7 @@ void doPrepare() { } } this.cost = cost + rows.size(); + isPrepared = true; } private void createTable() { diff --git a/h2/src/main/org/h2/engine/SessionLocal.java b/h2/src/main/org/h2/engine/SessionLocal.java index 753ee14465..97460abfd1 100644 --- a/h2/src/main/org/h2/engine/SessionLocal.java +++ b/h2/src/main/org/h2/engine/SessionLocal.java @@ -26,9 +26,10 @@ import org.h2.command.Parser; import org.h2.command.Prepared; import org.h2.command.ddl.Analyze; +import org.h2.command.query.Query; import org.h2.constraint.Constraint; import org.h2.index.Index; -import org.h2.index.ViewIndex; +import org.h2.index.QueryExpressionIndex; import org.h2.jdbc.JdbcConnection; import org.h2.jdbc.meta.DatabaseMeta; import org.h2.jdbc.meta.DatabaseMetaLocal; @@ -183,8 +184,8 @@ static Session getThreadLocalSession() { private SmallLRUCache queryCache; private long modificationMetaID = -1; private int createViewLevel; - private volatile SmallLRUCache viewIndexCache; - private HashMap subQueryIndexCache; + private volatile SmallLRUCache viewIndexCache; + private HashMap derivedTableIndexCache; private boolean lazyQueryExecution; private BitSet nonKeywords; @@ -580,6 +581,21 @@ public Prepared prepare(String sql, boolean rightsChecked, boolean literalsCheck return parser.prepare(sql); } + /** + * Parse a query and prepare its expressions. Rights and literals must be + * already checked. + * + * @param sql the SQL statement + * @return the prepared statement + */ + public Query prepareQueryExpression(String sql) { + Parser parser = new Parser(this); + parser.setRightsChecked(true); + parser.setLiteralsChecked(true); + return parser.prepareQueryExpression(sql); + } + + /** * Parse and prepare the given SQL statement. * This method also checks if the connection has been closed. @@ -614,8 +630,8 @@ public Command prepareLocal(String sql) { try { command = parser.prepareCommand(sql); } finally { - // we can't reuse sub-query indexes, so just drop the whole cache - subQueryIndexCache = null; + // we can't reuse indexes of derived tables, so just drop the whole cache + derivedTableIndexCache = null; } if (queryCache != null) { if (command.isCacheable()) { @@ -1485,24 +1501,25 @@ public void waitIfExclusiveModeEnabled() { } /** - * Get the view cache for this session. There are two caches: the subquery - * cache (which is only use for a single query, has no bounds, and is + * Get the view cache for this session. There are two caches: the derived + * table cache (which is only use for a single query, has no bounds, and is * cleared after use), and the cache for regular views. * - * @param subQuery true to get the subquery cache - * @return the view cache - */ - public Map getViewIndexCache(boolean subQuery) { - if (subQuery) { - // for sub-queries we don't need to use LRU because the cache should - // not grow too large for a single query (we drop the whole cache in - // the end of prepareLocal) - if (subQueryIndexCache == null) { - subQueryIndexCache = new HashMap<>(); + * @param derivedTable + * true to get the cache of derived tables + * @return the view cache or derived table cache + */ + public Map getViewIndexCache(boolean derivedTable) { + if (derivedTable) { + // for derived tables we don't need to use LRU because the cache + // should not grow too large for a single query (we drop the whole + // cache in this cache is dropped at the end of prepareLocal) + if (derivedTableIndexCache == null) { + derivedTableIndexCache = new HashMap<>(); } - return subQueryIndexCache; + return derivedTableIndexCache; } - SmallLRUCache cache = viewIndexCache; + SmallLRUCache cache = viewIndexCache; if (cache == null) { viewIndexCache = cache = SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE); } diff --git a/h2/src/main/org/h2/engine/User.java b/h2/src/main/org/h2/engine/User.java index 312516a84f..281d691ec1 100644 --- a/h2/src/main/org/h2/engine/User.java +++ b/h2/src/main/org/h2/engine/User.java @@ -18,7 +18,6 @@ import org.h2.table.RangeTable; import org.h2.table.Table; import org.h2.table.TableType; -import org.h2.table.TableView; import org.h2.util.MathUtils; import org.h2.util.StringUtils; import org.h2.util.Utils; @@ -221,15 +220,8 @@ public boolean hasTableRight(Table table, int rightMask) { return true; } TableType tableType = table.getTableType(); - if (TableType.VIEW == tableType) { - TableView v = (TableView) table; - if (v.getOwner() == this) { - // the owner of a view has access: - // SELECT * FROM (SELECT * FROM ...) - return true; - } - } else if (tableType == null) { - // function table + if (tableType == null) { + // derived or function table return true; } if (table.isTemporary() && !table.isGlobalTemporary()) { diff --git a/h2/src/main/org/h2/index/ViewCursor.java b/h2/src/main/org/h2/index/QueryExpressionCursor.java similarity index 88% rename from h2/src/main/org/h2/index/ViewCursor.java rename to h2/src/main/org/h2/index/QueryExpressionCursor.java index 53ac2a72ab..e8e3cb91d0 100644 --- a/h2/src/main/org/h2/index/ViewCursor.java +++ b/h2/src/main/org/h2/index/QueryExpressionCursor.java @@ -14,18 +14,17 @@ import org.h2.value.ValueNull; /** - * The cursor implementation of a view index. + * The cursor implementation of a query expression index. */ -public class ViewCursor implements Cursor { +public class QueryExpressionCursor implements Cursor { private final Table table; - private final ViewIndex index; + private final QueryExpressionIndex index; private final ResultInterface result; private final SearchRow first, last; private Row current; - public ViewCursor(ViewIndex index, ResultInterface result, SearchRow first, - SearchRow last) { + public QueryExpressionCursor(QueryExpressionIndex index, ResultInterface result, SearchRow first, SearchRow last) { this.table = index.getTable(); this.index = index; this.result = result; diff --git a/h2/src/main/org/h2/index/ViewIndex.java b/h2/src/main/org/h2/index/QueryExpressionIndex.java similarity index 89% rename from h2/src/main/org/h2/index/ViewIndex.java rename to h2/src/main/org/h2/index/QueryExpressionIndex.java index 173fe9a9b8..c1adef115d 100644 --- a/h2/src/main/org/h2/index/ViewIndex.java +++ b/h2/src/main/org/h2/index/QueryExpressionIndex.java @@ -25,21 +25,21 @@ import org.h2.result.SortOrder; import org.h2.table.Column; import org.h2.table.IndexColumn; +import org.h2.table.QueryExpressionTable; import org.h2.table.TableFilter; import org.h2.table.TableView; import org.h2.util.IntArray; import org.h2.value.Value; /** - * This object represents a virtual index for a query. - * Actually it only represents a prepared SELECT statement. + * This object represents a virtual index for a query expression. */ -public class ViewIndex extends Index implements SpatialIndex { +public class QueryExpressionIndex extends Index implements SpatialIndex { private static final long MAX_AGE_NANOS = TimeUnit.MILLISECONDS.toNanos(Constants.VIEW_COST_CACHE_MAX_AGE); - private final TableView view; + private final QueryExpressionTable table; private final String querySQL; private final ArrayList originalParameters; private boolean recursive; @@ -55,15 +55,15 @@ public class ViewIndex extends Index implements SpatialIndex { /** * Constructor for the original index in {@link TableView}. * - * @param view the table view + * @param table the query expression table * @param querySQL the query SQL * @param originalParameters the original parameters * @param recursive if the view is recursive */ - public ViewIndex(TableView view, String querySQL, + public QueryExpressionIndex(QueryExpressionTable table, String querySQL, ArrayList originalParameters, boolean recursive) { - super(view, 0, null, null, 0, IndexType.createNonUnique(false)); - this.view = view; + super(table, 0, null, null, 0, IndexType.createNonUnique(false)); + this.table = table; this.querySQL = querySQL; this.originalParameters = originalParameters; this.recursive = recursive; @@ -79,18 +79,18 @@ public ViewIndex(TableView view, String querySQL, * Constructor for plan item generation. Over this index the query will be * executed. * - * @param view the table view - * @param index the view index + * @param table the query expression table + * @param index the main index * @param session the session * @param masks the masks * @param filters table filters * @param filter current filter * @param sortOrder sort order */ - public ViewIndex(TableView view, ViewIndex index, SessionLocal session, + public QueryExpressionIndex(QueryExpressionTable table, QueryExpressionIndex index, SessionLocal session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder) { - super(view, 0, null, null, 0, IndexType.createNonUnique(false)); - this.view = view; + super(table, 0, null, null, 0, IndexType.createNonUnique(false)); + this.table = table; this.querySQL = index.querySQL; this.originalParameters = index.originalParameters; this.recursive = index.recursive; @@ -100,7 +100,7 @@ public ViewIndex(TableView view, ViewIndex index, SessionLocal session, if (!recursive) { query = getQuery(session, masks); } - if (recursive || view.getTopQuery() != null) { + if (recursive || table.getTopQuery() != null) { evaluatedAt = Long.MAX_VALUE; } else { long time = System.nanoTime(); @@ -117,8 +117,7 @@ public SessionLocal getSession() { public boolean isExpired() { assert evaluatedAt != Long.MIN_VALUE : "must not be called for main index of TableView"; - return !recursive && view.getTopQuery() == null && - System.nanoTime() - evaluatedAt > MAX_AGE_NANOS; + return !recursive && table.getTopQuery() == null && System.nanoTime() - evaluatedAt > MAX_AGE_NANOS; } @Override @@ -159,11 +158,11 @@ public Cursor findByGeometry(SessionLocal session, SearchRow first, SearchRow la } private Cursor findRecursive(SearchRow first, SearchRow last) { - assert recursive; + TableView view = (TableView) table; ResultInterface recursiveResult = view.getRecursiveResult(); if (recursiveResult != null) { recursiveResult.reset(); - return new ViewCursor(this, recursiveResult, first, last); + return new QueryExpressionCursor(this, recursiveResult, first, last); } if (query == null) { Parser parser = new Parser(createSession); @@ -210,7 +209,7 @@ private Cursor findRecursive(SearchRow first, SearchRow last) { } view.setRecursiveResult(null); localResult.done(); - return new ViewCursor(this, localResult, first, last); + return new QueryExpressionCursor(this, localResult, first, last); } /** @@ -243,7 +242,7 @@ public void setupQueryParameters(SessionLocal session, SearchRow first, SearchRo } else { len = 0; } - int idx = view.getParameterOffset(originalParameters); + int idx = table.getParameterOffset(originalParameters); for (int i = 0; i < len; i++) { int mask = indexMasks[i]; if ((mask & IndexCondition.EQUALITY) != 0) { @@ -268,7 +267,7 @@ private Cursor find(SessionLocal session, SearchRow first, SearchRow last, } setupQueryParameters(session, first, last, intersection); ResultInterface result = query.query(0); - return new ViewCursor(this, result, first, last); + return new QueryExpressionCursor(this, result, first, last); } private static void setParameter(ArrayList paramList, int x, @@ -287,14 +286,12 @@ public Query getQuery() { } private Query getQuery(SessionLocal session, int[] masks) { - Query q = (Query) session.prepare(querySQL, true, true); - if (masks == null) { + Query q = session.prepareQueryExpression(querySQL); + if (masks == null || !q.allowGlobalConditions()) { + q.preparePlan(); return q; } - if (!q.allowGlobalConditions()) { - return q; - } - int firstIndexParam = view.getParameterOffset(originalParameters); + int firstIndexParam = table.getParameterOffset(originalParameters); // the column index of each parameter // (for example: paramColumnIndex {0, 0} mean // param[0] is column 0, and param[1] is also column 0) @@ -369,9 +366,11 @@ private Query getQuery(SessionLocal session, int[] masks) { indexColumnId++; } } - String sql = q.getPlanSQL(DEFAULT_SQL_FLAGS); - q = (Query) session.prepare(sql, true, true); + if (!sql.equals(querySQL)) { + q = session.prepareQueryExpression(sql); + } + q.preparePlan(); return q; } diff --git a/h2/src/main/org/h2/table/DerivedTable.java b/h2/src/main/org/h2/table/DerivedTable.java new file mode 100644 index 0000000000..2f8cfc5f7b --- /dev/null +++ b/h2/src/main/org/h2/table/DerivedTable.java @@ -0,0 +1,94 @@ +/* + * 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.table; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.index.QueryExpressionIndex; +import org.h2.message.DbException; +import org.h2.util.StringUtils; + +/** + * A derived table. + */ +public final class DerivedTable extends QueryExpressionTable { + + private String querySQL; + + private Query topQuery; + + /** + * Create a derived table out of the given query. + * + * @param session the session + * @param name the view name + * @param columnTemplates column templates, or {@code null} + * @param query the initialized query + * @param topQuery the top level query + */ + public DerivedTable(SessionLocal session, String name, Column[] columnTemplates, Query query, Query topQuery) { + super(session.getDatabase().getMainSchema(), 0, name); + setTemporary(true); + this.topQuery = topQuery; + query.prepareExpressions(); + try { + this.querySQL = query.getPlanSQL(DEFAULT_SQL_FLAGS); + ArrayList params = query.getParameters(); + index = new QueryExpressionIndex(this, querySQL, params, false); + tables = new ArrayList<>(query.getTables()); + setColumns(initColumns(session, columnTemplates, query, true)); + viewQuery = query; + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.COLUMN_ALIAS_IS_NOT_SPECIFIED_1) { + throw e; + } + e.addSQL(getCreateSQL()); + throw e; + } + } + + @Override + public boolean isQueryComparable() { + if (!super.isQueryComparable()) { + return false; + } + if (topQuery != null && !topQuery.isEverything(ExpressionVisitor.QUERY_COMPARABLE_VISITOR)) { + return false; + } + return true; + } + + @Override + public boolean canDrop() { + return false; + } + + @Override + public TableType getTableType() { + return null; + } + + @Override + public Query getTopQuery() { + return topQuery; + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return StringUtils.indent(builder.append("(\n"), querySQL, 4, true).append(')'); + } + +} diff --git a/h2/src/main/org/h2/table/InformationSchemaTable.java b/h2/src/main/org/h2/table/InformationSchemaTable.java index f7957bbb63..736d1f311d 100644 --- a/h2/src/main/org/h2/table/InformationSchemaTable.java +++ b/h2/src/main/org/h2/table/InformationSchemaTable.java @@ -2403,7 +2403,7 @@ private void views(SessionLocal session, ArrayList rows, String catalog, Ta String viewDefinition, status = "VALID"; if (table instanceof TableView) { TableView view = (TableView) table; - viewDefinition = view.getQuery(); + viewDefinition = view.getQuerySQL(); if (view.isInvalid()) { status = "INVALID"; } diff --git a/h2/src/main/org/h2/table/QueryExpressionTable.java b/h2/src/main/org/h2/table/QueryExpressionTable.java new file mode 100644 index 0000000000..b7514e676a --- /dev/null +++ b/h2/src/main/org/h2/table/QueryExpressionTable.java @@ -0,0 +1,319 @@ +/* + * 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.table; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.command.query.Query; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.index.QueryExpressionIndex; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SortOrder; +import org.h2.schema.Schema; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A derived table or view. + */ +public abstract class QueryExpressionTable extends Table { + + /** + * The key of the index cache for views. + */ + static final class CacheKey { + + private final int[] masks; + + private final QueryExpressionTable queryExpressionTable; + + CacheKey(int[] masks, QueryExpressionTable queryExpressionTable) { + this.masks = masks; + this.queryExpressionTable = queryExpressionTable; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(masks); + result = prime * result + queryExpressionTable.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + CacheKey other = (CacheKey) obj; + if (queryExpressionTable != other.queryExpressionTable) { + return false; + } + return Arrays.equals(masks, other.masks); + } + } + + private static final long ROW_COUNT_APPROXIMATION = 100; + + /** + * Creates a list of column templates from a query (usually from WITH query, + * but could be any query) + * + * @param cols + * - an optional list of column names (can be specified by WITH + * clause overriding usual select names) + * @param theQuery + * - the query object we want the column list for + * @param querySQLOutput + * - array of length 1 to receive extra 'output' field in + * addition to return value - containing the SQL query of the + * Query object + * @return a list of column object returned by withQuery + */ + public static List createQueryColumnTemplateList(String[] cols, Query theQuery, String[] querySQLOutput) { + ArrayList columnTemplateList = new ArrayList<>(); + theQuery.prepare(); + // String array of length 1 is to receive extra 'output' field in + // addition to + // return value + querySQLOutput[0] = StringUtils.cache(theQuery.getPlanSQL(ADD_PLAN_INFORMATION)); + SessionLocal session = theQuery.getSession(); + ArrayList withExpressions = theQuery.getExpressions(); + for (int i = 0; i < withExpressions.size(); ++i) { + Expression columnExp = withExpressions.get(i); + // use the passed in column name if supplied, otherwise use alias + // (if found) otherwise use column name derived from column + // expression + String columnName = cols != null && cols.length > i ? cols[i] : columnExp.getColumnNameForView(session, i); + columnTemplateList.add(new Column(columnName, columnExp.getType())); + } + return columnTemplateList; + } + + static int getMaxParameterIndex(ArrayList parameters) { + int result = -1; + for (Parameter p : parameters) { + if (p != null) { + result = Math.max(result, p.getIndex()); + } + } + return result; + } + + Query viewQuery; + + QueryExpressionIndex index; + + ArrayList tables; + + private long lastModificationCheck; + + private long maxDataModificationId; + + QueryExpressionTable(Schema schema, int id, String name) { + super(schema, id, name, false, true); + } + + Column[] initColumns(SessionLocal session, Column[] columnTemplates, Query query, boolean isDerivedTable) { + ArrayList expressions = query.getExpressions(); + final int count = query.getColumnCount(); + ArrayList list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + Expression expr = expressions.get(i); + String name = null; + TypeInfo type = TypeInfo.TYPE_UNKNOWN; + if (columnTemplates != null && columnTemplates.length > i) { + name = columnTemplates[i].getName(); + type = columnTemplates[i].getType(); + } + if (name == null) { + name = isDerivedTable ? expr.getAlias(session, i) : expr.getColumnNameForView(session, i); + } + if (type.getValueType() == Value.UNKNOWN) { + type = expr.getType(); + } + list.add(new Column(name, type, this, i)); + } + return list.toArray(new Column[0]); + } + + public final Query getQuery() { + return viewQuery; + } + + public abstract Query getTopQuery(); + + @Override + public final void close(SessionLocal session) { + // nothing to do + } + + @Override + public final Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".addIndex"); + } + + @Override + public final boolean isView() { + return true; + } + + @Override + public final PlanItem getBestPlanItem(SessionLocal session, int[] masks, TableFilter[] filters, int filter, + SortOrder sortOrder, AllColumnsForPlan allColumnsSet) { + final CacheKey cacheKey = new CacheKey(masks, this); + Map indexCache = session.getViewIndexCache(getTableType() == null); + QueryExpressionIndex i = indexCache.get(cacheKey); + if (i == null || i.isExpired()) { + i = new QueryExpressionIndex(this, index, session, masks, filters, filter, sortOrder); + indexCache.put(cacheKey, i); + } + PlanItem item = new PlanItem(); + item.cost = i.getCost(session, masks, filters, filter, sortOrder, allColumnsSet); + item.setIndex(i); + return item; + } + + @Override + public boolean isQueryComparable() { + for (Table t : tables) { + if (!t.isQueryComparable()) { + return false; + } + } + return true; + } + + @Override + public final boolean isInsertable() { + return false; + } + + @Override + public final void removeRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".removeRow"); + } + + @Override + public final void addRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".addRow"); + } + + @Override + public final void checkSupportAlter() { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".checkSupportAlter"); + } + + @Override + public final long truncate(SessionLocal session) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".truncate"); + } + + @Override + public final long getRowCount(SessionLocal session) { + throw DbException.getInternalError(toString()); + } + + @Override + public final boolean canGetRowCount(SessionLocal session) { + // TODO could get the row count, but not that easy + return false; + } + + @Override + public final long getRowCountApproximation(SessionLocal session) { + return ROW_COUNT_APPROXIMATION; + } + + /** + * Get the index of the first parameter. + * + * @param additionalParameters + * additional parameters + * @return the index of the first parameter + */ + public final int getParameterOffset(ArrayList additionalParameters) { + Query topQuery = getTopQuery(); + int result = topQuery == null ? -1 : getMaxParameterIndex(topQuery.getParameters()); + if (additionalParameters != null) { + result = Math.max(result, getMaxParameterIndex(additionalParameters)); + } + return result + 1; + } + + @Override + public final boolean canReference() { + return false; + } + + @Override + public final ArrayList getIndexes() { + return null; + } + + @Override + public long getMaxDataModificationId() { + // if nothing was modified in the database since the last check, and the + // last is known, then we don't need to check again + // this speeds up nested views + long dbMod = database.getModificationDataId(); + if (dbMod > lastModificationCheck && maxDataModificationId <= dbMod) { + maxDataModificationId = viewQuery.getMaxDataModificationId(); + lastModificationCheck = dbMod; + } + return maxDataModificationId; + } + + @Override + public final Index getScanIndex(SessionLocal session) { + return getBestPlanItem(session, null, null, -1, null, null).getIndex(); + } + + @Override + public Index getScanIndex(SessionLocal session, int[] masks, TableFilter[] filters, int filter, // + SortOrder sortOrder, AllColumnsForPlan allColumnsSet) { + return getBestPlanItem(session, masks, filters, filter, sortOrder, allColumnsSet).getIndex(); + } + + @Override + public boolean isDeterministic() { + return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR); + } + + @Override + public final void addDependencies(HashSet dependencies) { + super.addDependencies(dependencies); + if (tables != null) { + for (Table t : tables) { + if (TableType.VIEW != t.getTableType()) { + t.addDependencies(dependencies); + } + } + } + } + +} diff --git a/h2/src/main/org/h2/table/TableView.java b/h2/src/main/org/h2/table/TableView.java index b6525bf915..e8850a6ada 100644 --- a/h2/src/main/org/h2/table/TableView.java +++ b/h2/src/main/org/h2/table/TableView.java @@ -7,9 +7,7 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Map; import org.h2.api.ErrorCode; import org.h2.command.Prepared; @@ -17,57 +15,37 @@ import org.h2.command.query.AllColumnsForPlan; import org.h2.command.query.Query; import org.h2.engine.Database; -import org.h2.engine.DbObject; import org.h2.engine.SessionLocal; -import org.h2.engine.User; -import org.h2.expression.Expression; -import org.h2.expression.ExpressionVisitor; import org.h2.expression.Parameter; import org.h2.index.Index; -import org.h2.index.IndexType; -import org.h2.index.ViewIndex; +import org.h2.index.QueryExpressionIndex; import org.h2.message.DbException; import org.h2.result.ResultInterface; -import org.h2.result.Row; import org.h2.result.SortOrder; import org.h2.schema.Schema; import org.h2.util.StringUtils; import org.h2.util.Utils; -import org.h2.value.TypeInfo; -import org.h2.value.Value; /** * A view is a virtual table that is defined by a query. * @author Thomas Mueller * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 */ -public class TableView extends Table { - - private static final long ROW_COUNT_APPROXIMATION = 100; +public final class TableView extends QueryExpressionTable { private String querySQL; - private ArrayList
          tables; private Column[] columnTemplates; - private Query viewQuery; - private ViewIndex index; private boolean allowRecursive; private DbException createException; - private long lastModificationCheck; - private long maxDataModificationId; - private User owner; - private Query topQuery; private ResultInterface recursiveResult; private boolean isRecursiveQueryDetected; private boolean isTableExpression; - private boolean isSubquery; public TableView(Schema schema, int id, String name, String querySQL, ArrayList params, Column[] columnTemplates, SessionLocal session, - boolean allowRecursive, boolean literalsChecked, boolean isTableExpression, boolean isTemporary, - boolean isSubquery) { - super(schema, id, name, false, true); + boolean allowRecursive, boolean literalsChecked, boolean isTableExpression, boolean isTemporary) { + super(schema, id, name); setTemporary(isTemporary); - this.isSubquery = isSubquery; init(querySQL, params, columnTemplates, session, allowRecursive, literalsChecked, isTableExpression); } @@ -105,7 +83,7 @@ private synchronized void init(String querySQL, ArrayList params, this.allowRecursive = allowRecursive; this.isRecursiveQueryDetected = false; this.isTableExpression = isTableExpression; - index = new ViewIndex(this, querySQL, params, allowRecursive); + index = new QueryExpressionIndex(this, querySQL, params, allowRecursive); initColumnsAndTables(session, literalsChecked); } @@ -168,26 +146,7 @@ private void initColumnsAndTables(SessionLocal session, boolean literalsChecked) Query compiledQuery = compileViewQuery(session, querySQL, literalsChecked); this.querySQL = compiledQuery.getPlanSQL(DEFAULT_SQL_FLAGS); tables = new ArrayList<>(compiledQuery.getTables()); - ArrayList expressions = compiledQuery.getExpressions(); - final int count = compiledQuery.getColumnCount(); - ArrayList list = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - Expression expr = expressions.get(i); - String name = null; - TypeInfo type = TypeInfo.TYPE_UNKNOWN; - if (columnTemplates != null && columnTemplates.length > i) { - name = columnTemplates[i].getName(); - type = columnTemplates[i].getType(); - } - if (name == null) { - name = isSubquery ? expr.getAlias(session, i) : expr.getColumnNameForView(session, i); - } - if (type.getValueType() == Value.UNKNOWN) { - type = expr.getType(); - } - list.add(new Column(name, type, this, i)); - } - cols = list.toArray(new Column[0]); + cols = initColumns(session, columnTemplates, compiledQuery, false); createException = null; viewQuery = compiledQuery; } catch (DbException e) { @@ -221,11 +180,6 @@ private void initColumnsAndTables(SessionLocal session, boolean literalsChecked) } } - @Override - public boolean isView() { - return true; - } - /** * Check if this view is currently invalid. * @@ -236,41 +190,8 @@ public boolean isInvalid() { } @Override - public PlanItem getBestPlanItem(SessionLocal session, int[] masks, - TableFilter[] filters, int filter, SortOrder sortOrder, - AllColumnsForPlan allColumnsSet) { - final CacheKey cacheKey = new CacheKey(masks, this); - Map indexCache = session.getViewIndexCache(topQuery != null); - ViewIndex i = indexCache.get(cacheKey); - if (i == null || i.isExpired()) { - i = new ViewIndex(this, index, session, masks, filters, filter, sortOrder); - indexCache.put(cacheKey, i); - } - PlanItem item = new PlanItem(); - item.cost = i.getCost(session, masks, filters, filter, sortOrder, allColumnsSet); - item.setIndex(i); - return item; - } - - @Override - public boolean isQueryComparable() { - if (!super.isQueryComparable()) { - return false; - } - for (Table t : tables) { - if (!t.isQueryComparable()) { - return false; - } - } - if (topQuery != null && - !topQuery.isEverything(ExpressionVisitor.QUERY_COMPARABLE_VISITOR)) { - return false; - } - return true; - } - public Query getTopQuery() { - return topQuery; + return null; } @Override @@ -329,53 +250,6 @@ private String getCreateSQL(boolean orReplace, boolean force, String quotedName) return builder.append(" AS\n").append(querySQL).toString(); } - @Override - public void close(SessionLocal session) { - // nothing to do - } - - @Override - public Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, - int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { - throw DbException.getUnsupportedException("VIEW"); - } - - @Override - public boolean isInsertable() { - return false; - } - - @Override - public void removeRow(SessionLocal session, Row row) { - throw DbException.getUnsupportedException("VIEW"); - } - - @Override - public void addRow(SessionLocal session, Row row) { - throw DbException.getUnsupportedException("VIEW"); - } - - @Override - public void checkSupportAlter() { - throw DbException.getUnsupportedException("VIEW"); - } - - @Override - public long truncate(SessionLocal session) { - throw DbException.getUnsupportedException("VIEW"); - } - - @Override - public long getRowCount(SessionLocal session) { - throw DbException.getInternalError(toString()); - } - - @Override - public boolean canGetRowCount(SessionLocal session) { - // TODO view: could get the row count, but not that easy - return false; - } - @Override public boolean canDrop() { return true; @@ -417,15 +291,10 @@ public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { return super.getSQL(builder, sqlFlags); } - public String getQuery() { + public String getQuerySQL() { return querySQL; } - @Override - public Index getScanIndex(SessionLocal session) { - return getBestPlanItem(session, null, null, -1, null, null).getIndex(); - } - @Override public Index getScanIndex(SessionLocal session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, @@ -434,37 +303,15 @@ public Index getScanIndex(SessionLocal session, int[] masks, String msg = createException.getMessage(); throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, createException, getTraceSQL(), msg); } - PlanItem item = getBestPlanItem(session, masks, filters, filter, sortOrder, allColumnsSet); - return item.getIndex(); - } - - @Override - public boolean canReference() { - return false; - } - - @Override - public ArrayList getIndexes() { - return null; + return super.getScanIndex(session, masks, filters, filter, sortOrder, allColumnsSet); } @Override public long getMaxDataModificationId() { - if (createException != null) { - return Long.MAX_VALUE; - } - if (viewQuery == null) { + if (createException != null || viewQuery == null) { return Long.MAX_VALUE; } - // if nothing was modified in the database since the last check, and the - // last is known, then we don't need to check again - // this speeds up nested views - long dbMod = database.getModificationDataId(); - if (dbMod > lastModificationCheck && maxDataModificationId <= dbMod) { - maxDataModificationId = viewQuery.getMaxDataModificationId(); - lastModificationCheck = dbMod; - } - return maxDataModificationId; + return super.getMaxDataModificationId(); } private void removeCurrentViewFromOtherTables() { @@ -482,75 +329,6 @@ private void addDependentViewToTables() { } } - private void setOwner(User owner) { - this.owner = owner; - } - - public User getOwner() { - return owner; - } - - /** - * Create a temporary view out of the given query. - * - * @param session the session - * @param owner the owner of the query - * @param name the view name - * @param columnTemplates column templates, or {@code null} - * @param query the prepared query - * @param topQuery the top level query - * @return the view table - */ - public static TableView createTempView(SessionLocal session, User owner, - String name, Column[] columnTemplates, Query query, Query topQuery) { - Schema mainSchema = session.getDatabase().getMainSchema(); - String querySQL = query.getPlanSQL(DEFAULT_SQL_FLAGS); - TableView v = new TableView(mainSchema, 0, name, - querySQL, query.getParameters(), columnTemplates, session, - false, true /* literals have already been checked when parsing original query */, - false, true, true); - if (v.createException != null) { - throw v.createException; - } - v.setTopQuery(topQuery); - v.setOwner(owner); - v.setTemporary(true); - return v; - } - - private void setTopQuery(Query topQuery) { - this.topQuery = topQuery; - } - - @Override - public long getRowCountApproximation(SessionLocal session) { - return ROW_COUNT_APPROXIMATION; - } - - /** - * Get the index of the first parameter. - * - * @param additionalParameters additional parameters - * @return the index of the first parameter - */ - public int getParameterOffset(ArrayList additionalParameters) { - int result = topQuery == null ? -1 : getMaxParameterIndex(topQuery.getParameters()); - if (additionalParameters != null) { - result = Math.max(result, getMaxParameterIndex(additionalParameters)); - } - return result + 1; - } - - private static int getMaxParameterIndex(ArrayList parameters) { - int result = -1; - for (Parameter p : parameters) { - if (p != null) { - result = Math.max(result, p.getIndex()); - } - } - return result; - } - public boolean isRecursive() { return allowRecursive; } @@ -560,7 +338,7 @@ public boolean isDeterministic() { if (allowRecursive || viewQuery == null) { return false; } - return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR); + return super.isDeterministic(); } public void setRecursiveResult(ResultInterface value) { @@ -574,59 +352,6 @@ public ResultInterface getRecursiveResult() { return recursiveResult; } - @Override - public void addDependencies(HashSet dependencies) { - super.addDependencies(dependencies); - if (tables != null) { - for (Table t : tables) { - if (TableType.VIEW != t.getTableType()) { - t.addDependencies(dependencies); - } - } - } - } - - /** - * The key of the index cache for views. - */ - private static final class CacheKey { - - private final int[] masks; - private final TableView view; - - CacheKey(int[] masks, TableView view) { - this.masks = masks; - this.view = view; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(masks); - result = prime * result + view.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - CacheKey other = (CacheKey) obj; - if (view != other.view) { - return false; - } - return Arrays.equals(masks, other.masks); - } - } - /** * Was query recursion detected during compiling. * @@ -693,7 +418,7 @@ public static TableView createTableViewMaybeRecursive(Schema schema, int id, Str if (!isTemporary) { withQuery.setSession(session); } - columnTemplateList = TableView.createQueryColumnTemplateList(columnNames.toArray(new String[1]), + columnTemplateList = createQueryColumnTemplateList(columnNames.toArray(new String[1]), (Query) withQuery, querySQLOutput); } finally { @@ -703,7 +428,7 @@ public static TableView createTableViewMaybeRecursive(Schema schema, int id, Str // build with recursion turned on TableView view = new TableView(schema, id, name, querySQL, parameters, columnTemplateList.toArray(columnTemplates), session, - true/* try recursive */, literalsChecked, isTableExpression, isTemporary, false); + true/* try recursive */, literalsChecked, isTableExpression, isTemporary); // is recursion really detected ? if not - recreate it without recursion flag // and no recursive index @@ -722,46 +447,12 @@ public static TableView createTableViewMaybeRecursive(Schema schema, int id, Str } view = new TableView(schema, id, name, querySQL, parameters, columnTemplates, session, - false/* detected not recursive */, literalsChecked, isTableExpression, isTemporary, false); + false/* detected not recursive */, literalsChecked, isTableExpression, isTemporary); } return view; } - - /** - * Creates a list of column templates from a query (usually from WITH query, - * but could be any query) - * - * @param cols - an optional list of column names (can be specified by WITH - * clause overriding usual select names) - * @param theQuery - the query object we want the column list for - * @param querySQLOutput - array of length 1 to receive extra 'output' field - * in addition to return value - containing the SQL query of the - * Query object - * @return a list of column object returned by withQuery - */ - public static List createQueryColumnTemplateList(String[] cols, - Query theQuery, String[] querySQLOutput) { - List columnTemplateList = new ArrayList<>(); - theQuery.prepare(); - // String array of length 1 is to receive extra 'output' field in addition to - // return value - querySQLOutput[0] = StringUtils.cache(theQuery.getPlanSQL(ADD_PLAN_INFORMATION)); - SessionLocal session = theQuery.getSession(); - ArrayList withExpressions = theQuery.getExpressions(); - for (int i = 0; i < withExpressions.size(); ++i) { - Expression columnExp = withExpressions.get(i); - // use the passed in column name if supplied, otherwise use alias - // (if found) otherwise use column name derived from column - // expression - String columnName = cols != null && cols.length > i ? cols[i] : columnExp.getColumnNameForView(session, i); - columnTemplateList.add(new Column(columnName, columnExp.getType())); - - } - return columnTemplateList; - } - /** * Create a table for a recursive query. * diff --git a/h2/src/test/org/h2/test/scripts/queries/select.sql b/h2/src/test/org/h2/test/scripts/queries/select.sql index 02c4d8e352..92689a6865 100644 --- a/h2/src/test/org/h2/test/scripts/queries/select.sql +++ b/h2/src/test/org/h2/test/scripts/queries/select.sql @@ -1184,3 +1184,30 @@ SELECT ((SELECT 1 X) EXCEPT (SELECT 1 Y)) T; > ---- > null > rows: 1 + +create table test(x0 int, x1 int); +> ok + +select * from + (select * from + (select * from + (select * from + (select * from + (select * from + (select * from + (select * from + (select * from test as t399 where x0 < 1 and x0 >= x0 or null <= -1) as t398 + where -1 is not distinct from -1) as t397 + where 3 is distinct from 2) as t396 + where null is distinct from -1) as t395 + where 3 is distinct from -1 or null = x1) as t394 + where x0 is distinct from null) as t393 + where x0 >= null and -1 <= 1 and 3 is not distinct from -1) as t392 + where -1 >= 3) as t391 +where -1 is distinct from -1 or 2 is distinct from x0; +> X0 X1 +> -- -- +> rows: 0 + +drop table test; +> ok From f20c9ad377ea1b19cb03e8b85625071891568d34 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 2 Feb 2022 12:09:45 +0800 Subject: [PATCH 15/93] Update pgjdbc to 42.3.2 --- h2/pom.xml | 2 +- h2/src/tools/org/h2/build/Build.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/h2/pom.xml b/h2/pom.xml index 36fdd1e2a7..1bc18cf054 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -44,7 +44,7 @@ 5.6.2 8.5.2 5.0.0 - 42.2.14 + 42.3.2 4.0.1 5.0.0 1.7.30 diff --git a/h2/src/tools/org/h2/build/Build.java b/h2/src/tools/org/h2/build/Build.java index db504f165f..21c3ea9719 100644 --- a/h2/src/tools/org/h2/build/Build.java +++ b/h2/src/tools/org/h2/build/Build.java @@ -53,9 +53,9 @@ public class Build extends BuildBase { private static final String OSGI_VERSION = "5.0.0"; - private static final String PGJDBC_VERSION = "42.2.14"; + private static final String PGJDBC_VERSION = "42.3.2"; - private static final String PGJDBC_HASH = "45fa6eef266aa80024ef2ab3688d9faa38c642e5"; + private static final String PGJDBC_HASH = "8fd7a20f008a58b97b26ba5c5084ee61602203aa"; private static final String JAVAX_SERVLET_VERSION = "4.0.1"; From 5d32251cbb964c60603876b9540d5d386433662a Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 2 Feb 2022 12:21:30 +0800 Subject: [PATCH 16/93] Parse inline NOT NULL constraint after referential constraint properly --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/command/Parser.java | 4 +++- h2/src/test/org/h2/test/scripts/ddl/createTable.sql | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 4031841c20..2c1daa3849 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 #3413: Parser can't parse REFERENCES … NOT NULL +
          • Issue #3410: OOME with nested derived tables
          • Issue #3405: Enhance SCRIPT to support operations on STDOUT diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 1a57a8d30b..d5550297f1 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -9249,7 +9249,9 @@ private void parseReferences(AlterTableAddConstraint command, } } if (readIf(NOT)) { - read("DEFERRABLE"); + if (!readIf("DEFERRABLE")) { + setTokenIndex(tokenIndex - 1); + } } else { readIf("DEFERRABLE"); } diff --git a/h2/src/test/org/h2/test/scripts/ddl/createTable.sql b/h2/src/test/org/h2/test/scripts/ddl/createTable.sql index 01d94e367a..0be538540b 100644 --- a/h2/src/test/org/h2/test/scripts/ddl/createTable.sql +++ b/h2/src/test/org/h2/test/scripts/ddl/createTable.sql @@ -265,3 +265,15 @@ TABLE TEST; DROP TABLE TEST; > ok + +CREATE TABLE T1(ID BIGINT PRIMARY KEY); +> ok + +CREATE TABLE T2(ID BIGINT PRIMARY KEY, R BIGINT REFERENCES T1 NOT NULL); +> ok + +SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'T2' AND COLUMN_NAME = 'R'; +>> NO + +DROP TABLE T2, T1; +> ok From 4b732bc1349446153a2af65cd38fda9c8a0d09b8 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 2 Feb 2022 17:12:56 +0800 Subject: [PATCH 17/93] Add support for reading binary numeric values in PgServerThread --- .../main/org/h2/server/pg/PgServerThread.java | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/h2/src/main/org/h2/server/pg/PgServerThread.java b/h2/src/main/org/h2/server/pg/PgServerThread.java index 4e243f8a8d..5e49cd3b9a 100644 --- a/h2/src/main/org/h2/server/pg/PgServerThread.java +++ b/h2/src/main/org/h2/server/pg/PgServerThread.java @@ -56,9 +56,11 @@ 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.ValueInteger; import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; import org.h2.value.ValueReal; import org.h2.value.ValueSmallint; import org.h2.value.ValueTime; @@ -743,6 +745,10 @@ private void writeDataColumn(Value v, int pgType, boolean text) throws IOExcepti private static final int[] POWERS10 = {1, 10, 100, 1000, 10000}; private static final int MAX_GROUP_SCALE = 4; private static final int MAX_GROUP_SIZE = POWERS10[4]; + private static final short NUMERIC_POSITIVE = 0x0000; + private static final short NUMERIC_NEGATIVE = 0x4000; + private static final short NUMERIC_NAN = (short) 0xC000; + private static final BigInteger NUMERIC_CHUNK_MULTIPLIER = BigInteger.valueOf(10_000L); private static int divide(BigInteger[] unscaled, int divisor) { BigInteger[] bi = unscaled[0].divideAndRemainder(BigInteger.valueOf(divisor)); @@ -795,7 +801,7 @@ private void writeNumericBinary(BigDecimal value) throws IOException { writeInt(8 + groupCount * 2); writeShort(groupCount); writeShort(groupCount + weight); - writeShort(signum < 0 ? 16384 : 0); + writeShort(signum < 0 ? NUMERIC_NEGATIVE : NUMERIC_POSITIVE); writeShort(scale); for (int i = groupCount - 1; i >= 0; i--) { writeShort(groups.get(i)); @@ -895,16 +901,20 @@ private void setParameter(ArrayList parameters, in checkParamLength(8, paramLen); value = ValueDouble.get(dataIn.readDouble()); break; - case PgServer.PG_TYPE_BYTEA: - byte[] d1 = Utils.newBytes(paramLen); - readFully(d1); - value = ValueVarbinary.getNoCopy(d1); + case PgServer.PG_TYPE_BYTEA: { + byte[] d = Utils.newBytes(paramLen); + readFully(d); + value = ValueVarbinary.getNoCopy(d); + break; + } + case PgServer.PG_TYPE_NUMERIC: + value = readNumericBinary(paramLen); break; default: server.trace("Binary format for type: "+pgType+" is unsupported"); - byte[] d2 = Utils.newBytes(paramLen); - readFully(d2); - value = ValueVarchar.get(new String(d2, getEncoding()), session); + byte[] d = Utils.newBytes(paramLen); + readFully(d); + value = ValueVarchar.get(new String(d, getEncoding()), session); } } parameters.get(i).setValue(value, true); @@ -916,6 +926,40 @@ private static void checkParamLength(int expected, int got) { } } + private Value readNumericBinary(int paramLen) throws IOException { + if (paramLen < 8) { + throw DbException.getInvalidValueException("numeric binary length", paramLen); + } + short len = readShort(); + short weight = readShort(); + short sign = readShort(); + short scale = readShort(); + if (len * 2 + 8 != paramLen) { + throw DbException.getInvalidValueException("numeric binary length", paramLen); + } + if (sign == NUMERIC_NAN) { + return ValueDecfloat.NAN; + } + if (sign != NUMERIC_POSITIVE && sign != NUMERIC_NEGATIVE) { + throw DbException.getInvalidValueException("numeric sign", sign); + } + if ((scale & 0x3FFF) != scale) { + throw DbException.getInvalidValueException("numeric scale", scale); + } + if (len == 0) { + return scale == 0 ? ValueNumeric.ZERO : ValueNumeric.get(new BigDecimal(BigInteger.ZERO, scale)); + } + BigInteger n = BigInteger.ZERO; + for (int i = 0; i < len; i++) { + short c = readShort(); + if (c < 0 || c > 9_999) { + throw DbException.getInvalidValueException("numeric chunk", c); + } + n = n.multiply(NUMERIC_CHUNK_MULTIPLIER).add(BigInteger.valueOf(c)); + } + return ValueNumeric.get(new BigDecimal(n, (len - weight - 1) * 4).setScale(scale)); + } + private void sendErrorOrCancelResponse(Exception e) throws IOException { if (e instanceof DbException && ((DbException) e).getErrorCode() == ErrorCode.STATEMENT_WAS_CANCELED) { sendCancelQueryResponse(); From ebd2aa439ef71ba85920b7a26b715ce1415b2c04 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 2 Feb 2022 22:18:19 +0800 Subject: [PATCH 18/93] Fix NPE in Query.getParameterValues() --- h2/src/docsrc/html/changelog.html | 3 +++ h2/src/main/org/h2/command/query/Query.java | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 2c1daa3849..09016e66c2 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,6 +21,9 @@

            Change Log

            Next Version (unreleased)

              +
            • Issue #3414: H2 2.1.210: Query with Parameters throws NPE at +org.h2.command.query.Query.getParameterValues(Query.java:449) +
            • Issue #3413: Parser can't parse REFERENCES … NOT NULL
            • Issue #3410: OOME with nested derived tables diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java index 697575c8b3..5d45222fca 100644 --- a/h2/src/main/org/h2/command/query/Query.java +++ b/h2/src/main/org/h2/command/query/Query.java @@ -447,7 +447,8 @@ private boolean sameResultAsLast(Value[] params, Value[] lastParams, long lastEv } for (int i = 0; i < params.length; i++) { Value a = lastParams[i], b = params[i]; - if (a.getValueType() != b.getValueType() || !session.areEqual(a, b)) { + // Derived tables can have gaps in parameters + if (a != null && !a.equals(b)) { return false; } } @@ -462,8 +463,9 @@ private Value[] getParameterValues() { int size = list.size(); Value[] params = new Value[size]; for (int i = 0; i < size; i++) { - Value v = list.get(i).getParamValue(); - params[i] = v; + Parameter parameter = list.get(i); + // Derived tables can have gaps in parameters + params[i] = parameter != null ? parameter.getParamValue() : null; } return params; } From 35c26887a78f23bb90ff6c216838841883146333 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Thu, 3 Feb 2022 12:41:58 +0800 Subject: [PATCH 19/93] Fix PgServerThread.readNumericBinary() --- h2/src/main/org/h2/server/pg/PgServerThread.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/h2/src/main/org/h2/server/pg/PgServerThread.java b/h2/src/main/org/h2/server/pg/PgServerThread.java index 5e49cd3b9a..acff786ecd 100644 --- a/h2/src/main/org/h2/server/pg/PgServerThread.java +++ b/h2/src/main/org/h2/server/pg/PgServerThread.java @@ -957,6 +957,9 @@ private Value readNumericBinary(int paramLen) throws IOException { } n = n.multiply(NUMERIC_CHUNK_MULTIPLIER).add(BigInteger.valueOf(c)); } + if (sign != NUMERIC_POSITIVE) { + n = n.negate(); + } return ValueNumeric.get(new BigDecimal(n, (len - weight - 1) * 4).setScale(scale)); } From 968b6c15a6abed641327aab4b5d9e06e9d9c239e Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Thu, 3 Feb 2022 12:43:00 +0800 Subject: [PATCH 20/93] Allow combination of any geometry types with the same SRID --- h2/src/main/org/h2/api/ErrorCode.java | 2 +- h2/src/main/org/h2/value/TypeInfo.java | 40 +++++++++++++++++ .../h2/test/scripts/datatypes/geometry.sql | 45 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/api/ErrorCode.java b/h2/src/main/org/h2/api/ErrorCode.java index bb74ebef80..ac92a50002 100644 --- a/h2/src/main/org/h2/api/ErrorCode.java +++ b/h2/src/main/org/h2/api/ErrorCode.java @@ -1714,7 +1714,7 @@ public class ErrorCode { /** * The error with code 90110 is thrown when - * trying to compare values of incomparable data types. + * trying to compare or combine values of incomparable data types. * Example: *
                    * CREATE TABLE test (id INT NOT NULL, name VARCHAR);
              diff --git a/h2/src/main/org/h2/value/TypeInfo.java b/h2/src/main/org/h2/value/TypeInfo.java
              index cc607a239b..4411d970e7 100644
              --- a/h2/src/main/org/h2/value/TypeInfo.java
              +++ b/h2/src/main/org/h2/value/TypeInfo.java
              @@ -609,6 +609,8 @@ public static TypeInfo getHigherType(TypeInfo type1, TypeInfo type2) {
                       case Value.DOUBLE:
                           precision = -1L;
                           break;
              +        case Value.GEOMETRY:
              +            return getHigherGeometry(type1, type2);
                       case Value.ARRAY:
                           return getHigherArray(type1, type2, dimensions(type1), dimensions(type2));
                       case Value.ROW:
              @@ -623,6 +625,44 @@ public static TypeInfo getHigherType(TypeInfo type1, TypeInfo type2) {
                               dataType == t1 && ext1 != null ? ext1 : dataType == t2 ? type2.extTypeInfo : null);
                   }
               
              +    private static TypeInfo getHigherGeometry(TypeInfo type1, TypeInfo type2) {
              +        ExtTypeInfo ext1 = type1.getExtTypeInfo(), ext2 = type2.getExtTypeInfo();
              +        if (ext1 instanceof ExtTypeInfoGeometry) {
              +            if (ext2 instanceof ExtTypeInfoGeometry) {
              +                ExtTypeInfoGeometry g1 = (ExtTypeInfoGeometry) ext1, g2 = (ExtTypeInfoGeometry) ext2;
              +                Integer srid = g1.getSrid();
              +                if (!Objects.equals(srid, g2.getSrid())) {
              +                    throw DbException.get(ErrorCode.TYPES_ARE_NOT_COMPARABLE_2, type1.getTraceSQL(),
              +                            type2.getTraceSQL());
              +                }
              +                if (g1.getType() == g2.getType()) {
              +                    return type1;
              +                }
              +                return srid == null ? TypeInfo.TYPE_GEOMETRY
              +                        : TypeInfo.getTypeInfo(Value.GEOMETRY, -1, -1, new ExtTypeInfoGeometry(0, srid));
              +            } else {
              +                return getHigherGeometry(type1, ext1, type2);
              +            }
              +        } else if (ext2 instanceof ExtTypeInfoGeometry) {
              +            return getHigherGeometry(type2, ext2, type1);
              +        } else {
              +            return TypeInfo.TYPE_GEOMETRY;
              +        }
              +    }
              +
              +    private static TypeInfo getHigherGeometry(TypeInfo geometryType, ExtTypeInfo geometryExt, TypeInfo otherType) {
              +        if (otherType.getValueType() != Value.GEOMETRY) {
              +            return geometryType;
              +        }
              +        ExtTypeInfoGeometry g = (ExtTypeInfoGeometry) geometryExt;
              +        if (g.getType() == 0) {
              +            return geometryType;
              +        }
              +        Integer srid = g.getSrid();
              +        return srid == null ? TypeInfo.TYPE_GEOMETRY
              +                : TypeInfo.getTypeInfo(Value.GEOMETRY, -1, -1, new ExtTypeInfoGeometry(0, srid));
              +    }
              +
                   private static int dimensions(TypeInfo type) {
                       int result;
                       for (result = 0; type.getValueType() == Value.ARRAY; result++) {
              diff --git a/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql b/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql
              index 4b6675bf74..14a3522c76 100644
              --- a/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql
              +++ b/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql
              @@ -275,3 +275,48 @@ SELECT CAST(GEOMETRY X'00000000030000000100000000' AS VARBINARY);
               
               VALUES GEOMETRY 'POINT (1 2 3)';
               > exception DATA_CONVERSION_ERROR_1
              +
              +VALUES CAST('SRID=1;POINT(1 1)' AS GEOMETRY(POINT, 1)) UNION VALUES CAST('SRID=1;POINT Z(1 1 1)' AS GEOMETRY(POINT Z, 1));
              +> C1
              +> ----------------------
              +> SRID=1;POINT (1 1)
              +> SRID=1;POINT Z (1 1 1)
              +> rows: 2
              +
              +VALUES CAST('SRID=1;POINT(1 1)' AS GEOMETRY(POINT, 1)) UNION VALUES CAST('SRID=2;POINT Z(1 1 1)' AS GEOMETRY(POINT Z, 2));
              +> exception TYPES_ARE_NOT_COMPARABLE_2
              +
              +VALUES CAST('SRID=1;POINT(1 1)' AS GEOMETRY(POINT, 1)) UNION VALUES CAST('SRID=1;POINT (2 2)' AS GEOMETRY(POINT, 1));
              +> C1
              +> ------------------
              +> SRID=1;POINT (1 1)
              +> SRID=1;POINT (2 2)
              +> rows: 2
              +
              +VALUES CAST('POINT(1 1)' AS GEOMETRY(GEOMETRY, 0)) UNION VALUES CAST('POINT (2 2)' AS GEOMETRY);
              +> C1
              +> -----------
              +> POINT (1 1)
              +> POINT (2 2)
              +> rows: 2
              +
              +VALUES CAST('POINT(1 1)' AS GEOMETRY(POINT)) UNION VALUES CAST('POINT Z (1 1 1)' AS GEOMETRY(POINT Z));
              +> C1
              +> ---------------
              +> POINT (1 1)
              +> POINT Z (1 1 1)
              +> rows: 2
              +
              +VALUES CAST('SRID=1;POINT(1 1)' AS GEOMETRY(POINT, 1)) UNION VALUES NULL;
              +> C1
              +> ------------------
              +> SRID=1;POINT (1 1)
              +> null
              +> rows: 2
              +
              +VALUES NULL UNION VALUES CAST('POINT(1 1)' AS GEOMETRY(POINT));
              +> C1
              +> -----------
              +> POINT (1 1)
              +> null
              +> rows: 2
              
              From 45913f1503d428fe3c2e2008f94601f778ca1d7a Mon Sep 17 00:00:00 2001
              From: Evgenij Ryazanov 
              Date: Sat, 5 Feb 2022 10:16:22 +0800
              Subject: [PATCH 21/93] Allow BIT(1) again in MySQL and MariaDB compatibility
               modes
              
              ---
               h2/src/docsrc/html/changelog.html                     | 4 ++++
               h2/src/main/org/h2/command/Parser.java                | 3 ++-
               h2/src/test/org/h2/test/scripts/datatypes/boolean.sql | 9 +++++++++
               3 files changed, 15 insertions(+), 1 deletion(-)
              
              diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html
              index 09016e66c2..de98ff7499 100644
              --- a/h2/src/docsrc/html/changelog.html
              +++ b/h2/src/docsrc/html/changelog.html
              @@ -21,6 +21,10 @@ 

              Change Log

              Next Version (unreleased)

                +
              • Issue #3426: Regression: BIT(1) is not accepted in MySQL compatibility mode +
              • +
              • PR #3422: Allow combination of any geometry types with the same SRID +
              • Issue #3414: H2 2.1.210: Query with Parameters throws NPE at org.h2.command.query.Query.getParameterValues(Query.java:449)
              • diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index d5550297f1..02d7eab980 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -6317,7 +6317,8 @@ private TypeInfo readIfDataType1() { } read(CLOSE_PAREN); } - if (mode.allNumericTypesHavePrecision && DataType.isNumericType(dataType.type)) { + if (mode.allNumericTypesHavePrecision + && (DataType.isNumericType(dataType.type) || dataType.type == Value.BOOLEAN)) { if (readIf(OPEN_PAREN)) { // Support for MySQL: INT(11), MEDIUMINT(8) and so on. // Just ignore the precision. diff --git a/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql b/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql index 979a5e7385..727d7aa08a 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql @@ -40,3 +40,12 @@ EXPLAIN VALUES (TRUE, FALSE, UNKNOWN); EXPLAIN SELECT A IS TRUE OR B IS FALSE FROM (VALUES (TRUE, TRUE)) T(A, B); >> SELECT ("A" IS TRUE) OR ("B" IS FALSE) FROM (VALUES (TRUE, TRUE)) "T"("A", "B") /* table scan */ + +SET MODE MySQL; +> ok + +CREATE TABLE TEST(A BIT(1)); +> ok + +DROP TABLE TEST; +> ok From dbe6021ec0175adddcc9c2268916c957401812d1 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 16 Feb 2022 18:27:13 +0800 Subject: [PATCH 22/93] Add conversion from ENUM to JSON --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/value/Value.java | 1 + h2/src/test/org/h2/test/scripts/datatypes/enum.sql | 3 +++ 3 files changed, 6 insertions(+) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index de98ff7499..7302d5caad 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 #3439: Cannot use enum values in JSON without explicit casts +
                • Issue #3426: Regression: BIT(1) is not accepted in MySQL compatibility mode
                • PR #3422: Allow combination of any geometry types with the same SRID diff --git a/h2/src/main/org/h2/value/Value.java b/h2/src/main/org/h2/value/Value.java index dfc57e3100..1ed983fe41 100644 --- a/h2/src/main/org/h2/value/Value.java +++ b/h2/src/main/org/h2/value/Value.java @@ -2393,6 +2393,7 @@ private ValueJson convertToJson(TypeInfo targetType, int conversionMode, Object case DATE: case TIME: case TIME_TZ: + case ENUM: case UUID: v = ValueJson.get(getString()); break; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/enum.sql b/h2/src/test/org/h2/test/scripts/datatypes/enum.sql index cd10233159..5325fecccd 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/enum.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/enum.sql @@ -57,6 +57,9 @@ select suit, count(rank) from card group by suit order by suit, count(rank); > diamonds 1 > rows (ordered): 4 +SELECT JSON_ARRAYAGG(DISTINCT SUIT ORDER BY SUIT) FROM CARD; +>> ["hearts","clubs","diamonds"] + select rank from card where suit = 'diamonds'; >> 8 From 9c5909b3ce22db86cdfe09ba12224518b98a9ed4 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 14 Mar 2022 12:54:46 -0400 Subject: [PATCH 23/93] remove pagestore leftovers --- .../main/org/h2/value/lob/LobDataDatabase.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/h2/src/main/org/h2/value/lob/LobDataDatabase.java b/h2/src/main/org/h2/value/lob/LobDataDatabase.java index 648fad12a3..893b5eb1d6 100644 --- a/h2/src/main/org/h2/value/lob/LobDataDatabase.java +++ b/h2/src/main/org/h2/value/lob/LobDataDatabase.java @@ -17,7 +17,7 @@ */ public final class LobDataDatabase extends LobData { - private DataHandler handler; + private final DataHandler handler; /** * If the LOB is managed by the one the LobStorageBackend classes, these are @@ -27,11 +27,6 @@ public final class LobDataDatabase extends LobData { private final long lobId; - /** - * Fix for recovery tool. - */ - private boolean isRecoveryReference; - public LobDataDatabase(DataHandler handler, int tableId, long lobId) { this.handler = handler; this.tableId = tableId; @@ -87,13 +82,4 @@ public DataHandler getDataHandler() { public String toString() { return "lob-table: table: " + tableId + " id: " + lobId; } - - public void setRecoveryReference(boolean isRecoveryReference) { - this.isRecoveryReference = isRecoveryReference; - } - - public boolean isRecoveryReference() { - return isRecoveryReference; - } - } From 610bc5a5b0acb23e09644b16206915ec6da5b220 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 14 Mar 2022 12:55:36 -0400 Subject: [PATCH 24/93] delayed lob removal --- h2/src/main/org/h2/mvstore/MVStore.java | 38 ++++++++++++-- .../main/org/h2/mvstore/db/LobStorageMap.java | 45 +++++++++++++--- h2/src/test/org/h2/test/db/TestLob.java | 52 ++++++++++++++++++- 3 files changed, 124 insertions(+), 11 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 46daadd11e..9667aeb993 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.LongConsumer; import java.util.function.Predicate; import java.util.function.Supplier; import org.h2.compress.CompressDeflate; @@ -1305,6 +1306,7 @@ private void closeStore(boolean normalShutdown, int allowedCompactionTime) { try { try { if (normalShutdown && fileStore != null && !fileStore.isReadOnly()) { + notifyLobCleaner(currentVersion); for (MVMap map : maps.values()) { if (map.isClosed()) { deregisterMapRoot(map.getId()); @@ -2774,6 +2776,29 @@ private void setOldestVersionToKeep(long oldestVersionToKeep) { success = oldestVersionToKeep <= current || this.oldestVersionToKeep.compareAndSet(current, oldestVersionToKeep); } while (!success); + notifyAboutOldestVersion(oldestVersionToKeep); + } + + private void notifyAboutOldestVersion(long oldestVersionToKeep) { + if (onVersionListener != null && bufferSaveExecutor != null) { + Runnable blobCleaner = () -> { + notifyLobCleaner(oldestVersionToKeep); + }; + try { + bufferSaveExecutor.execute(blobCleaner); + } catch (RejectedExecutionException ignore) { + } catch (MVStoreException e) { + panic(e); + } catch (Throwable e) { + panic(DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, "{0}", e.toString(), e)); + } + } + } + + public void notifyLobCleaner(long oldestVersionToKeep) { + if (onVersionListener != null) { + onVersionListener.accept(oldestVersionToKeep); + } } private long lastChunkVersion() { @@ -3329,8 +3354,7 @@ public boolean isClosed() { } storeLock.lock(); try { - assert state == STATE_CLOSED; - return true; + return state == STATE_CLOSED; } finally { storeLock.unlock(); } @@ -3592,6 +3616,13 @@ public void deregisterVersionUsage(TxCounter txCounter) { } } + + private LongConsumer onVersionListener; + + public void setOnVersionListener(LongConsumer onVersionListener) { + this.onVersionListener = onVersionListener; + } + private void onVersionChange(long version) { TxCounter txCounter = currentTxCounter; assert txCounter.get() >= 0; @@ -3607,7 +3638,8 @@ private void dropUnusedVersions() { && txCounter.get() < 0) { versions.poll(); } - setOldestVersionToKeep((txCounter != null ? txCounter : currentTxCounter).version); + long oldestVersionToKeep = (txCounter != null ? txCounter : currentTxCounter).version; + setOldestVersionToKeep(oldestVersionToKeep); } private int dropUnusedChunks() { diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index 16d74229ae..9c27ffa2d5 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -15,6 +15,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import org.h2.api.ErrorCode; import org.h2.engine.Database; @@ -78,6 +80,7 @@ public final class LobStorageMap implements LobStorageInterface private final StreamStore streamStore; + private final Queue pendingLobRemovals = new ConcurrentLinkedQueue<>(); /** * Open map used to store LOB metadata @@ -102,6 +105,7 @@ public LobStorageMap(Database database) { Store s = database.getStore(); TransactionStore txStore = s.getTransactionStore(); mvStore = s.getMvStore(); + mvStore.setOnVersionListener(this::removeLobs); MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { lobMap = openLobMap(txStore); @@ -355,7 +359,7 @@ public void removeAllForTable(int tableId) { final Iterator iter = tempLobMap.keyIterator(0L); while (iter.hasNext()) { long lobId = iter.next(); - removeLob(tableId, lobId); + doRemoveLob(tableId, lobId); } tempLobMap.clear(); } else { @@ -370,7 +374,7 @@ public void removeAllForTable(int tableId) { } } for (long lobId : list) { - removeLob(tableId, lobId); + doRemoveLob(tableId, lobId); } } } finally { @@ -380,18 +384,32 @@ public void removeAllForTable(int tableId) { @Override public void removeLob(ValueLob lob) { + LobDataDatabase lobData = (LobDataDatabase) lob.getLobData(); + int tableId = lobData.getTableId(); + long lobId = lobData.getLobId(); + requestLobRemoval(tableId, lobId); + } + + private void requestLobRemoval(int tableId, long lobId) { + pendingLobRemovals.offer(new LobRemovalInfo(mvStore.getCurrentVersion(), lobId, tableId)); + } + + private void removeLobs(long oldestVersionToKeep) { MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { - LobDataDatabase lobData = (LobDataDatabase) lob.getLobData(); - int tableId = lobData.getTableId(); - long lobId = lobData.getLobId(); - removeLob(tableId, lobId); + LobRemovalInfo lobRemovalInfo; + while ((lobRemovalInfo = pendingLobRemovals.poll()) != null && lobRemovalInfo.version < oldestVersionToKeep) { + doRemoveLob(lobRemovalInfo.mapId, lobRemovalInfo.lobId); + } + if (lobRemovalInfo != null) { + pendingLobRemovals.offer(lobRemovalInfo); + } } finally { mvStore.deregisterVersionUsage(txCounter); } } - private void removeLob(int tableId, long lobId) { + public void doRemoveLob(int tableId, long lobId) { if (TRACE) { trace("remove " + tableId + "/" + lobId); } @@ -560,4 +578,17 @@ public BlobMeta[] createStorage(int size) { } } } + + private static final class LobRemovalInfo + { + final long version; + final long lobId; + final int mapId; + + LobRemovalInfo(long version, long lobId, int mapId) { + this.version = version; + this.lobId = lobId; + this.mapId = mapId; + } + } } diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java index 45203921a2..18ac766b71 100644 --- a/h2/src/test/org/h2/test/db/TestLob.java +++ b/h2/src/test/org/h2/test/db/TestLob.java @@ -70,6 +70,7 @@ public static void main(String... a) throws Exception { @Override public void test() throws Exception { + testConcurrentSelectAndUpdate(); testReclamationOnInDoubtRollback(); testRemoveAfterDeleteAndClose(); testRemovedAfterTimeout(); @@ -252,8 +253,9 @@ private void testRemovedAfterTimeout() throws Exception { Thread.sleep(250); // start a new transaction, to be sure stat.execute("delete from test"); - assertThrows(SQLException.class, c1).getSubString(1, 3); + c1.getSubString(1, 3); conn.close(); + assertThrows(SQLException.class, c1).getSubString(1, 3); } private void testConcurrentRemoveRead() throws Exception { @@ -1578,4 +1580,52 @@ private void testLimitsLarge(byte[] b, String s, ValueLob v) throws IOException assertEquals(s, IOUtils.readStringAndClose(v.getReader(), -1)); } } + + public void testConcurrentSelectAndUpdate() throws SQLException, InterruptedException { + deleteDb("lob"); + final JdbcConnection conn1 = (JdbcConnection) getConnection("lob"); + final JdbcConnection conn2 = (JdbcConnection) getConnection("lob"); + + try (Statement st = conn1.createStatement()) { + final String createTable = "create table t1 (id int, ver bigint, data text, primary key (id));"; + st.execute(createTable); +// st.execute("SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ"); + } + + final String insert = "insert into t1 (id, ver, data) values (1, 0, ?)"; + try (final PreparedStatement insertStmt = conn1.prepareStatement(insert)) { + final String largeData = org.h2.util.StringUtils.pad("", 512, "x", false); + insertStmt.setString(1, largeData); + insertStmt.executeUpdate(); + } + + final long startTime_ms = System.currentTimeMillis(); + final long endTime_ms = startTime_ms + 10_000; // 10 seconds + + final Thread thread1 = new Thread() { + @Override + public void run() { + try { + final String update = "update t1 set ver = ver + 1 where id = 1"; + try (PreparedStatement ps = conn2.prepareStatement(update)) { + while (!Thread.currentThread().isInterrupted() && System.currentTimeMillis() < endTime_ms) { + ps.executeUpdate(); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + thread1.start(); + + try (final PreparedStatement st = conn1.prepareStatement("select * from t1 where id = 1")) { + while (System.currentTimeMillis() < endTime_ms) { + st.executeQuery(); + } + } + thread1.join(); + conn1.close(); + conn2.close(); + } } From 1291b0e39601c40c884c8f091bbd9aa379aca84a Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 14 Mar 2022 14:25:10 -0400 Subject: [PATCH 25/93] avoid infinite recursion --- h2/src/main/org/h2/mvstore/MVStore.java | 27 ++++++++++++------- .../main/org/h2/mvstore/db/LobStorageMap.java | 3 ++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 9667aeb993..8a2b4504c5 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -3601,21 +3601,30 @@ public TxCounter registerVersionUsage() { * @param txCounter to be decremented, obtained from registerVersionUsage() */ public void deregisterVersionUsage(TxCounter txCounter) { - if(txCounter != null) { - if(txCounter.decrementAndGet() <= 0) { - if (storeLock.isHeldByCurrentThread()) { + if(decrementVersionUsageCounter(txCounter)) { + if (storeLock.isHeldByCurrentThread()) { + dropUnusedVersions(); + } else if (storeLock.tryLock()) { + try { dropUnusedVersions(); - } else if (storeLock.tryLock()) { - try { - dropUnusedVersions(); - } finally { - storeLock.unlock(); - } + } finally { + storeLock.unlock(); } } } } + /** + * De-register (close) completed operation (transaction). + * This will decrement usage counter for the corresponding version. + * + * @param txCounter to be decremented, obtained from registerVersionUsage() + * @return true if counter reaches zero, which indicates that version is no longer in use, false otherwise. + */ + public boolean decrementVersionUsageCounter(TxCounter txCounter) { + return txCounter != null && txCounter.decrementAndGet() <= 0; + } + private LongConsumer onVersionListener; diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index 9c27ffa2d5..5c33364a67 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -405,7 +405,8 @@ private void removeLobs(long oldestVersionToKeep) { pendingLobRemovals.offer(lobRemovalInfo); } } finally { - mvStore.deregisterVersionUsage(txCounter); + // we can not call deregisterVersionUsage() due to a possible infinite recursion + mvStore.decrementVersionUsageCounter(txCounter); } } From f58d0d4c4237afde10e9f6ca5068e49dd481d317 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 14 Mar 2022 17:55:08 -0400 Subject: [PATCH 26/93] avoid needless async cleanup call --- h2/src/main/org/h2/mvstore/MVStore.java | 51 ++++++++++++++----- .../main/org/h2/mvstore/db/LobStorageMap.java | 12 +++-- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 8a2b4504c5..0a35542032 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -39,7 +39,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.LongConsumer; import java.util.function.Predicate; import java.util.function.Supplier; import org.h2.compress.CompressDeflate; @@ -365,6 +364,11 @@ public class MVStore implements AutoCloseable { private long leafCount; private long nonLeafCount; + /** + * Callback for maintance after some unused store versions were dropped + */ + private Cleaner cleaner; + /** * Create and open the store. @@ -1306,7 +1310,8 @@ private void closeStore(boolean normalShutdown, int allowedCompactionTime) { try { try { if (normalShutdown && fileStore != null && !fileStore.isReadOnly()) { - notifyLobCleaner(currentVersion); + // remove all dead LOBs before maps are closed + notifyCleaner(currentVersion); for (MVMap map : maps.values()) { if (map.isClosed()) { deregisterMapRoot(map.getId()); @@ -2779,10 +2784,14 @@ private void setOldestVersionToKeep(long oldestVersionToKeep) { notifyAboutOldestVersion(oldestVersionToKeep); } + public void setCleaner(Cleaner cleaner) { + this.cleaner = cleaner; + } + private void notifyAboutOldestVersion(long oldestVersionToKeep) { - if (onVersionListener != null && bufferSaveExecutor != null) { + if (cleaner != null && cleaner.needCleanup()) { Runnable blobCleaner = () -> { - notifyLobCleaner(oldestVersionToKeep); + notifyCleaner(oldestVersionToKeep); }; try { bufferSaveExecutor.execute(blobCleaner); @@ -2795,9 +2804,9 @@ private void notifyAboutOldestVersion(long oldestVersionToKeep) { } } - public void notifyLobCleaner(long oldestVersionToKeep) { - if (onVersionListener != null) { - onVersionListener.accept(oldestVersionToKeep); + public void notifyCleaner(long oldestVersionToKeep) { + if (cleaner != null && cleaner.needCleanup()) { + cleaner.cleanup(oldestVersionToKeep); } } @@ -3625,13 +3634,6 @@ public boolean decrementVersionUsageCounter(TxCounter txCounter) { return txCounter != null && txCounter.decrementAndGet() <= 0; } - - private LongConsumer onVersionListener; - - public void setOnVersionListener(LongConsumer onVersionListener) { - this.onVersionListener = onVersionListener; - } - private void onVersionChange(long version) { TxCounter txCounter = currentTxCounter; assert txCounter.get() >= 0; @@ -4114,4 +4116,25 @@ public static Builder fromString(String s) { return new Builder((HashMap) DataUtils.parseMap(s)); } } + + /** + * Callback interface to perform cleanup after some unused store versions were dropped. + * Currently removes LOBs, which are known to be out of scope. + */ + public interface Cleaner { + /** + * Determine if cleanup is needed. + * This is mainly performance optimization to avoid async call to cleanup(). + * + * @return true if cleanup is required at this time, false otherwise + */ + boolean needCleanup(); + + /** + * Actual procedure for cleanup after some unused store versions were dropped + * + * @param oldestVersionToKeep in this MVStore + */ + void cleanup(long oldestVersionToKeep); + } } diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index 5c33364a67..cd62c1cba8 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -48,7 +48,7 @@ * This class stores LOB objects in the database, in maps. This is the back-end * i.e. the server side of the LOB storage. */ -public final class LobStorageMap implements LobStorageInterface +public final class LobStorageMap implements LobStorageInterface, MVStore.Cleaner { private static final boolean TRACE = false; @@ -105,7 +105,7 @@ public LobStorageMap(Database database) { Store s = database.getStore(); TransactionStore txStore = s.getTransactionStore(); mvStore = s.getMvStore(); - mvStore.setOnVersionListener(this::removeLobs); + mvStore.setCleaner(this); MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { lobMap = openLobMap(txStore); @@ -394,7 +394,13 @@ private void requestLobRemoval(int tableId, long lobId) { pendingLobRemovals.offer(new LobRemovalInfo(mvStore.getCurrentVersion(), lobId, tableId)); } - private void removeLobs(long oldestVersionToKeep) { + @Override + public boolean needCleanup() { + return !pendingLobRemovals.isEmpty(); + } + + @Override + public void cleanup(long oldestVersionToKeep) { MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { LobRemovalInfo lobRemovalInfo; From 6de85c8b69ad8acce33a3b0177030ad88b75492c Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 14 Mar 2022 19:56:24 -0400 Subject: [PATCH 27/93] fix NPE --- h2/src/main/org/h2/mvstore/MVStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 0a35542032..8e13e136cb 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -2789,7 +2789,7 @@ public void setCleaner(Cleaner cleaner) { } private void notifyAboutOldestVersion(long oldestVersionToKeep) { - if (cleaner != null && cleaner.needCleanup()) { + if (cleaner != null && cleaner.needCleanup() && bufferSaveExecutor != null) { Runnable blobCleaner = () -> { notifyCleaner(oldestVersionToKeep); }; From 5bb3a58b71721ac98fa191f2de59ffd7974348eb Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 14 Mar 2022 23:51:41 -0400 Subject: [PATCH 28/93] changelog update --- h2/src/docsrc/html/changelog.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 7302d5caad..8f6b322ac5 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 #1808: Occasional NPE in concurrent update of LOB +
                  • Issue #3439: Cannot use enum values in JSON without explicit casts
                  • Issue #3426: Regression: BIT(1) is not accepted in MySQL compatibility mode From 52e3b9052cc2a07ea3221e8d1371ac4ba38dd667 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Tue, 15 Mar 2022 14:47:25 -0400 Subject: [PATCH 29/93] typo, test code cleanup --- h2/src/main/org/h2/mvstore/MVStore.java | 2 +- h2/src/test/org/h2/test/db/TestLob.java | 65 ++++++++++++------------- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 8e13e136cb..374c7579c7 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -365,7 +365,7 @@ public class MVStore implements AutoCloseable { private long nonLeafCount; /** - * Callback for maintance after some unused store versions were dropped + * Callback for maintenance after some unused store versions were dropped */ private Cleaner cleaner; diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java index 18ac766b71..fa71ee6dfa 100644 --- a/h2/src/test/org/h2/test/db/TestLob.java +++ b/h2/src/test/org/h2/test/db/TestLob.java @@ -1583,49 +1583,44 @@ private void testLimitsLarge(byte[] b, String s, ValueLob v) throws IOException public void testConcurrentSelectAndUpdate() throws SQLException, InterruptedException { deleteDb("lob"); - final JdbcConnection conn1 = (JdbcConnection) getConnection("lob"); - final JdbcConnection conn2 = (JdbcConnection) getConnection("lob"); + try (JdbcConnection conn1 = (JdbcConnection) getConnection("lob")) { + try (JdbcConnection conn2 = (JdbcConnection) getConnection("lob")) { - try (Statement st = conn1.createStatement()) { - final String createTable = "create table t1 (id int, ver bigint, data text, primary key (id));"; - st.execute(createTable); -// st.execute("SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ"); - } + try (Statement st = conn1.createStatement()) { + String createTable = "create table t1 (id int, ver bigint, data text, primary key (id));"; + st.execute(createTable); + } - final String insert = "insert into t1 (id, ver, data) values (1, 0, ?)"; - try (final PreparedStatement insertStmt = conn1.prepareStatement(insert)) { - final String largeData = org.h2.util.StringUtils.pad("", 512, "x", false); - insertStmt.setString(1, largeData); - insertStmt.executeUpdate(); - } + String insert = "insert into t1 (id, ver, data) values (1, 0, ?)"; + try (PreparedStatement insertStmt = conn1.prepareStatement(insert)) { + String largeData = org.h2.util.StringUtils.pad("", 512, "x", false); + insertStmt.setString(1, largeData); + insertStmt.executeUpdate(); + } - final long startTime_ms = System.currentTimeMillis(); - final long endTime_ms = startTime_ms + 10_000; // 10 seconds + long startTimeNs = System.nanoTime(); - final Thread thread1 = new Thread() { - @Override - public void run() { - try { - final String update = "update t1 set ver = ver + 1 where id = 1"; - try (PreparedStatement ps = conn2.prepareStatement(update)) { - while (!Thread.currentThread().isInterrupted() && System.currentTimeMillis() < endTime_ms) { - ps.executeUpdate(); + Thread thread1 = new Thread(() -> { + try { + String update = "update t1 set ver = ver + 1 where id = 1"; + try (PreparedStatement ps = conn2.prepareStatement(update)) { + while (!Thread.currentThread().isInterrupted() && System.nanoTime() - startTimeNs < 10_000_000_000L) { + ps.executeUpdate(); + } } + } catch (Exception e) { + throw new RuntimeException(e); } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }; - thread1.start(); + }); + thread1.start(); - try (final PreparedStatement st = conn1.prepareStatement("select * from t1 where id = 1")) { - while (System.currentTimeMillis() < endTime_ms) { - st.executeQuery(); + try (PreparedStatement st = conn1.prepareStatement("select * from t1 where id = 1")) { + while (System.nanoTime() - startTimeNs < 10_000_000_000L) { + st.executeQuery(); + } + } + thread1.join(); } } - thread1.join(); - conn1.close(); - conn2.close(); } } From c65f5fe15d4e2060c7bf4ca1ff54a2b390514743 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sun, 20 Mar 2022 15:10:30 -0400 Subject: [PATCH 30/93] fix bug in readStoreHeader() --- h2/src/main/org/h2/mvstore/MVStore.java | 1 + 1 file changed, 1 insertion(+) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 374c7579c7..318586ebc7 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -937,6 +937,7 @@ private void readStoreHeader() { Chunk tailChunk = discoverChunk(blocksInStore); if (tailChunk != null) { blocksInStore = tailChunk.block; // for a possible full scan later on + validChunksByLocation.put(blocksInStore, tailChunk); if (newest == null || tailChunk.version > newest.version) { newest = tailChunk; } From efc60acbe3c5aee111da597af72caee03286a5ed Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 13:59:39 +0200 Subject: [PATCH 31/93] #3457 increase max length of VAR* types --- h2/src/docsrc/html/advanced.html | 4 ++-- h2/src/docsrc/html/migration-to-v2.html | 4 ++-- h2/src/main/org/h2/engine/Constants.java | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/h2/src/docsrc/html/advanced.html b/h2/src/docsrc/html/advanced.html index 68e865b1ff..b4e1d3fc55 100644 --- a/h2/src/docsrc/html/advanced.html +++ b/h2/src/docsrc/html/advanced.html @@ -1813,9 +1813,9 @@

                    Limits and Limitations

                    The actual possible number can be smaller if their definitions are too long.
                  • The maximum length of an identifier (table name, column name, and so on) is 256 characters.
                  • The maximum length of CHARACTER, CHARACTER VARYING and VARCHAR_IGNORECASE values and columns -is 1048576 characters. +is 1_000_000_000 characters.
                  • The maximum length of BINARY, BINARY VARYING, JAVA_OBJECT, GEOMETRY, and JSON values and columns -is 1048576 bytes. +is 1_000_000_000 bytes.
                  • The maximum precision of NUMERIC and DECFLOAT values and columns is 100000.
                  • The maximum length of an ENUM value is 1048576 characters, the maximum number of ENUM values is 65536.
                  • The maximum cardinality of an ARRAY value or column is 65536. diff --git a/h2/src/docsrc/html/migration-to-v2.html b/h2/src/docsrc/html/migration-to-v2.html index 47ec8ecd9e..915bccb4ba 100644 --- a/h2/src/docsrc/html/migration-to-v2.html +++ b/h2/src/docsrc/html/migration-to-v2.html @@ -76,7 +76,7 @@

                    Data types

                    The maximum length of CHARACTER and CHARACTER VARYING data types -is n 1,048,576 characters. For larger values use +is 1_000_000_000 characters. For larger values use CHARACTER LARGE OBJECT.

                    @@ -84,7 +84,7 @@

                    Data types

                    BINARY and BINARY VARYING are now different data types. BINARY means fixed-length data type and its default length is 1. -The maximum length of binary strings is 1,048,576 bytes. For larger values use +The maximum length of binary strings is 1_000_000_000 bytes. For larger values use BINARY LARGE OBJECT

                    diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index 00b986728b..de5b323226 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -267,8 +267,11 @@ public class Constants { /** * The maximum allowed length for character string, binary string, and other * data types based on them; excluding LOB data types. + *

                    + * This needs to be less than (2^31-8)/2 to avoid running into the limit on + * encoding data fields when storing rows. */ - public static final int MAX_STRING_LENGTH = 1024 * 1024; + public static final int MAX_STRING_LENGTH = 1024 * 1024 * 1024; /** * The maximum allowed precision of numeric data types. From b0589a9eb7654a463bf3e155de0b0685b3ce80f8 Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 15:19:53 +0200 Subject: [PATCH 32/93] fix constant to match the new documented value --- h2/src/main/org/h2/engine/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index de5b323226..becd10a21a 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -271,7 +271,7 @@ public class Constants { * This needs to be less than (2^31-8)/2 to avoid running into the limit on * encoding data fields when storing rows. */ - public static final int MAX_STRING_LENGTH = 1024 * 1024 * 1024; + public static final int MAX_STRING_LENGTH = 1000_000_000; /** * The maximum allowed precision of numeric data types. From f3aa31ff8e1bd50aca60977e680aa545dc693af9 Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 15:20:14 +0200 Subject: [PATCH 33/93] reduce re-allocation in getListaggTruncate which should speed it up --- h2/src/main/org/h2/expression/aggregate/Aggregate.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/expression/aggregate/Aggregate.java b/h2/src/main/org/h2/expression/aggregate/Aggregate.java index ac8082c354..a31e43f7e7 100644 --- a/h2/src/main/org/h2/expression/aggregate/Aggregate.java +++ b/h2/src/main/org/h2/expression/aggregate/Aggregate.java @@ -813,7 +813,9 @@ private StringBuilder getListaggTruncate(Value[] array, String separator, String String[] strings = new String[count]; String s = getListaggItem(array[0]); strings[0] = s; - StringBuilder builder = new StringBuilder(s); + final int estimatedLength = (int) Math.min(Integer.MAX_VALUE, s.length() * (long)count); + final StringBuilder builder = new StringBuilder(estimatedLength); + builder.append(s); loop: for (int i = 1; i < count; i++) { builder.append(separator).append(strings[i] = s = getListaggItem(array[i])); int length = builder.length(); From fecfe018d42260870aff1a33a61e1641907028a6 Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 15:22:15 +0200 Subject: [PATCH 34/93] remove LISTAGG oiverflow tests there is no way to test this without using up so much memory we'd probably get flagged for abusing CI --- .../scripts/functions/aggregate/listagg.sql | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/listagg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/listagg.sql index 1a0d91f1a9..073650b612 100644 --- a/h2/src/test/org/h2/test/scripts/functions/aggregate/listagg.sql +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/listagg.sql @@ -216,40 +216,3 @@ EXPLAIN SELECT LISTAGG(V ON OVERFLOW TRUNCATE '..' WITHOUT COUNT) WITHIN GROUP ( DROP TABLE TEST; > ok - -CREATE TABLE TEST(V VARCHAR) AS SELECT 'ABCD_EFGH_' || X FROM SYSTEM_RANGE(1, 70000); -> ok - -SELECT RIGHT(LISTAGG(V ON OVERFLOW TRUNCATE WITH COUNT) WITHIN GROUP(ORDER BY V), 40) FROM TEST; ->> BCD_EFGH_69391,ABCD_EFGH_69392,...(4007) - -SELECT RIGHT(LISTAGG(V ON OVERFLOW TRUNCATE WITHOUT COUNT) WITHIN GROUP(ORDER BY V), 40) FROM TEST; ->> 9391,ABCD_EFGH_69392,ABCD_EFGH_69393,... - -SELECT RIGHT(LISTAGG(V ON OVERFLOW TRUNCATE '~~~~~~~~~~~~~~~' WITH COUNT) WITHIN GROUP(ORDER BY V), 40) FROM TEST; ->> 90,ABCD_EFGH_69391,~~~~~~~~~~~~~~~(4008) - -TRUNCATE TABLE TEST; -> update count: 70000 - -INSERT INTO TEST VALUES REPEAT('A', 1048573); -> update count: 1 - -SELECT RIGHT(LISTAGG(V ON OVERFLOW TRUNCATE WITH COUNT) WITHIN GROUP(ORDER BY V), 40) FROM - (TABLE TEST UNION VALUES 'BB'); ->> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,BB - -SELECT RIGHT(LISTAGG(V ON OVERFLOW ERROR) WITHIN GROUP(ORDER BY V), 40) FROM - (TABLE TEST UNION VALUES 'BBB'); -> exception VALUE_TOO_LONG_2 - -SELECT RIGHT(LISTAGG(V ON OVERFLOW TRUNCATE WITH COUNT) WITHIN GROUP(ORDER BY V), 40) FROM - (TABLE TEST UNION VALUES 'BBB'); ->> ...(2) - -SELECT RIGHT(LISTAGG(V ON OVERFLOW TRUNCATE '..' WITHOUT COUNT) WITHIN GROUP(ORDER BY V), 40) FROM - (TABLE TEST UNION VALUES 'BBB'); ->> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,.. - -DROP TABLE TEST; -> ok From 770baea4ad3a9ef3ebf2df1a35134d8ab5947df8 Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 15:31:32 +0200 Subject: [PATCH 35/93] fix overflow --- h2/src/main/org/h2/value/ValueBlob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/value/ValueBlob.java b/h2/src/main/org/h2/value/ValueBlob.java index 86879f5ee3..fcfbed1dac 100644 --- a/h2/src/main/org/h2/value/ValueBlob.java +++ b/h2/src/main/org/h2/value/ValueBlob.java @@ -126,7 +126,7 @@ public String getString() { return readString((int) p); } // 1 Java character may be encoded with up to 3 bytes - if (octetLength > Constants.MAX_STRING_LENGTH * 3) { + if (octetLength > Constants.MAX_STRING_LENGTH * 3L) { throw getStringTooLong(charLength()); } String s; From ab3d39246152a5bf7d466f8e38300d99c286349c Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 15:31:40 +0200 Subject: [PATCH 36/93] fix tests --- h2/src/test/org/h2/test/scripts/datatypes/binary.sql | 10 +++++----- h2/src/test/org/h2/test/scripts/datatypes/char.sql | 10 +++++----- .../test/org/h2/test/scripts/datatypes/java_object.sql | 10 +++++----- h2/src/test/org/h2/test/scripts/datatypes/json.sql | 10 +++++----- .../test/org/h2/test/scripts/datatypes/varbinary.sql | 10 +++++----- .../h2/test/scripts/datatypes/varchar-ignorecase.sql | 10 +++++----- h2/src/test/org/h2/test/scripts/datatypes/varchar.sql | 10 +++++----- h2/src/test/org/h2/test/scripts/testScript.sql | 6 +++--- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/h2/src/test/org/h2/test/scripts/datatypes/binary.sql b/h2/src/test/org/h2/test/scripts/datatypes/binary.sql index fadf19999c..49c31eadeb 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/binary.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/binary.sql @@ -32,23 +32,23 @@ CREATE TABLE T(C BINARY(0)); VALUES CAST(X'0102' AS BINARY); >> X'01' -CREATE TABLE T1(A BINARY(1048576)); +CREATE TABLE T1(A BINARY(1000000000)); > ok -CREATE TABLE T2(A BINARY(1048577)); +CREATE TABLE T2(A BINARY(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A BINARY(1048577)); +CREATE TABLE T2(A BINARY(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_OCTET_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_OCTET_LENGTH > ---------- ---------------------- -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/char.sql b/h2/src/test/org/h2/test/scripts/datatypes/char.sql index c76241463a..45a4f89647 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/char.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/char.sql @@ -172,23 +172,23 @@ DROP TABLE TEST; SET MODE Regular; > ok -CREATE TABLE T1(A CHARACTER(1048576)); +CREATE TABLE T1(A CHARACTER(1000000000)); > ok -CREATE TABLE T2(A CHARACTER(1048577)); +CREATE TABLE T2(A CHARACTER(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A CHARACTER(1048577)); +CREATE TABLE T2(A CHARACTER(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_MAXIMUM_LENGTH > ---------- ------------------------ -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/java_object.sql b/h2/src/test/org/h2/test/scripts/datatypes/java_object.sql index bbe0f8ece9..cfa4678913 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/java_object.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/java_object.sql @@ -27,23 +27,23 @@ VALUES CAST(CAST (X'0000' AS JAVA_OBJECT) AS JAVA_OBJECT(1)); CREATE TABLE T(C JAVA_OBJECT(0)); > exception INVALID_VALUE_2 -CREATE TABLE T1(A JAVA_OBJECT(1048576)); +CREATE TABLE T1(A JAVA_OBJECT(1000000000)); > ok -CREATE TABLE T2(A JAVA_OBJECT(1048577)); +CREATE TABLE T2(A JAVA_OBJECT(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A JAVA_OBJECT(1048577)); +CREATE TABLE T2(A JAVA_OBJECT(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_OCTET_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_OCTET_LENGTH > ---------- ---------------------- -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/json.sql b/h2/src/test/org/h2/test/scripts/datatypes/json.sql index 4bf8ece132..0ad674cfe1 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/json.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/json.sql @@ -284,23 +284,23 @@ SET MODE Regular; EXPLAIN SELECT A IS JSON AND B IS JSON FROM (VALUES (JSON 'null', 1)) T(A, B); >> SELECT ("A" IS JSON) AND ("B" IS JSON) FROM (VALUES (JSON 'null', 1)) "T"("A", "B") /* table scan */ -CREATE TABLE T1(A JSON(1048576)); +CREATE TABLE T1(A JSON(1000000000)); > ok -CREATE TABLE T2(A JSON(1048577)); +CREATE TABLE T2(A JSON(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A JSON(1048577)); +CREATE TABLE T2(A JSON(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_OCTET_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_OCTET_LENGTH > ---------- ---------------------- -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql b/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql index 881b3a7923..4801af08c5 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql @@ -114,23 +114,23 @@ EXPLAIN VALUES X''; CREATE TABLE T(C VARBINARY(0)); > exception INVALID_VALUE_2 -CREATE TABLE T1(A BINARY VARYING(1048576)); +CREATE TABLE T1(A BINARY VARYING(1000000000)); > ok -CREATE TABLE T2(A BINARY VARYING(1048577)); +CREATE TABLE T2(A BINARY VARYING(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A BINARY VARYING(1048577)); +CREATE TABLE T2(A BINARY VARYING(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_OCTET_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_OCTET_LENGTH > ---------- ---------------------- -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/varchar-ignorecase.sql b/h2/src/test/org/h2/test/scripts/datatypes/varchar-ignorecase.sql index 268b906706..5878465c3a 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/varchar-ignorecase.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/varchar-ignorecase.sql @@ -165,23 +165,23 @@ SET COLLATION OFF; > ok -CREATE TABLE T1(A VARCHAR_IGNORECASE(1048576)); +CREATE TABLE T1(A VARCHAR_IGNORECASE(1000000000)); > ok -CREATE TABLE T2(A VARCHAR_IGNORECASE(1048577)); +CREATE TABLE T2(A VARCHAR_IGNORECASE(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A VARCHAR_IGNORECASE(1048577)); +CREATE TABLE T2(A VARCHAR_IGNORECASE(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_MAXIMUM_LENGTH > ---------- ------------------------ -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql b/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql index d7ebecfa0b..dd85699a3f 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql @@ -49,23 +49,23 @@ DROP TABLE T; > ok -CREATE TABLE T1(A CHARACTER VARYING(1048576)); +CREATE TABLE T1(A CHARACTER VARYING(1000000000)); > ok -CREATE TABLE T2(A CHARACTER VARYING(1048577)); +CREATE TABLE T2(A CHARACTER VARYING(1000000001)); > exception INVALID_VALUE_PRECISION SET TRUNCATE_LARGE_LENGTH TRUE; > ok -CREATE TABLE T2(A CHARACTER VARYING(1048577)); +CREATE TABLE T2(A CHARACTER VARYING(1000000000)); > ok SELECT TABLE_NAME, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; > TABLE_NAME CHARACTER_MAXIMUM_LENGTH > ---------- ------------------------ -> T1 1048576 -> T2 1048576 +> T1 1000000000 +> T2 1000000000 > rows: 2 SET TRUNCATE_LARGE_LENGTH FALSE; diff --git a/h2/src/test/org/h2/test/scripts/testScript.sql b/h2/src/test/org/h2/test/scripts/testScript.sql index dd74558e9e..5415e3b9f7 100644 --- a/h2/src/test/org/h2/test/scripts/testScript.sql +++ b/h2/src/test/org/h2/test/scripts/testScript.sql @@ -2178,9 +2178,9 @@ select DOMAIN_NAME, DOMAIN_DEFAULT, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, PARENT_ > EMAIL null CHARACTER VARYING 200 null null > GMAIL '@gmail.com' CHARACTER VARYING 200 EMAIL null > STRING '' CHARACTER VARYING 255 null null -> STRING1 null CHARACTER VARYING 1048576 null null -> STRING2 '' CHARACTER VARYING 1048576 null null -> STRING_X null CHARACTER VARYING 1048576 STRING2 null +> STRING1 null CHARACTER VARYING 1000000000 null null +> STRING2 '' CHARACTER VARYING 1000000000 null null +> STRING_X null CHARACTER VARYING 1000000000 STRING2 null > rows: 6 script nodata nopasswords nosettings noversion; From da22c7986fea3e1b0a195659779ccc9378c5dbce Mon Sep 17 00:00:00 2001 From: Noel Grandin Date: Thu, 24 Mar 2022 16:11:27 +0200 Subject: [PATCH 37/93] disable LOB testLimits test causes OOM on CI --- h2/src/test/org/h2/test/db/TestLob.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java index fa71ee6dfa..076c98db4a 100644 --- a/h2/src/test/org/h2/test/db/TestLob.java +++ b/h2/src/test/org/h2/test/db/TestLob.java @@ -117,7 +117,8 @@ public void test() throws Exception { testLob(true); testJavaObject(); testLobInValueResultSet(); - testLimits(); + // cannot run this on CI, will cause OOM + // testLimits(); deleteDb("lob"); } From 5483ba763bf7735e80544c4f19addc9bf2ccef12 Mon Sep 17 00:00:00 2001 From: Marius Volkhart Date: Wed, 16 Mar 2022 12:49:20 -0400 Subject: [PATCH 38/93] Add performance tests for SQLite Configure SQLite to run in a mode comparable to H2. The performance docs are updated with approximations of SQLite performance on these tests, but not with detailed numbers. --- h2/src/docsrc/html/performance.html | 13 ++++++++++--- h2/src/test/org/h2/test/bench/Database.java | 14 ++++++++++++++ h2/src/test/org/h2/test/bench/test.properties | 12 ++++++------ h2/src/tools/org/h2/build/Build.java | 9 ++++++++- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/h2/src/docsrc/html/performance.html b/h2/src/docsrc/html/performance.html index 54d1b4ba15..0643b3cc1f 100644 --- a/h2/src/docsrc/html/performance.html +++ b/h2/src/docsrc/html/performance.html @@ -158,9 +158,16 @@

                    MySQL

                    SQLite

                    -SQLite 3.36.0.2 was tested, but the results are not published currently, -because it's about 50 times slower than H2 in embedded mode. -Any tips on how to configure SQLite for higher performance are welcome. +SQLite 3.36.0.3, configured to use WAL and with +synchronous=NORMAL was tested in a +separate, less reliable run. A rough estimate is that SQLite performs approximately 2-5x worse in the simple benchmarks, +which perform simple work in the database, resulting in a low work-per-transaction ration. SQLite becomes competitive as +the complexity of the database interactions increases. The results seemed to vary drastically across machine, and more +reliable results should be obtained. Benchmark on your production hardware. +

                    +

                    +The benchmarks used include multi-threaded scenarios, and we were not able to get the SQLite JDBC driver we used to work +with them. Help with configuring the driver for multi-threaded usage is welcome.

                    Firebird

                    diff --git a/h2/src/test/org/h2/test/bench/Database.java b/h2/src/test/org/h2/test/bench/Database.java index 000f30cd47..69c7e4e232 100644 --- a/h2/src/test/org/h2/test/bench/Database.java +++ b/h2/src/test/org/h2/test/bench/Database.java @@ -226,6 +226,20 @@ Connection openNewConnection() throws SQLException { try (Statement s = newConn.createStatement()) { s.execute("SET WRITE_DELAY 1"); } + } else if (url.startsWith("jdbc:sqlite:")) { + try (Statement s = newConn.createStatement()) { + + // Since 2010, SQLite has a Write-Ahead Logging mode which is widely cited as the key to getting good + // performance from SQLite. This option replaces the rollback journaling mode. Additional + // files are created as part of this mode. https://sqlite.org/wal.html + s.execute("PRAGMA journal_mode=WAL;"); + + // In WAL mode, NORMAL is safe from corruption and is consistent, but mayNot be durable in the event of + // a power loss. From the SQLite docs, "A transaction committed in WAL mode with synchronous=NORMAL + // might roll back following a power loss or system crash." This is in line with H2's commit delay. + // https://sqlite.org/pragma.html#pragma_synchronous + s.execute("PRAGMA synchronous=NORMAL;"); + } } return newConn; } diff --git a/h2/src/test/org/h2/test/bench/test.properties b/h2/src/test/org/h2/test/bench/test.properties index f81e595fe3..82b0805e10 100644 --- a/h2/src/test/org/h2/test/bench/test.properties +++ b/h2/src/test/org/h2/test/bench/test.properties @@ -8,6 +8,7 @@ db1 = H2, org.h2.Driver, jdbc:h2:./data/test, sa, sa db2 = HSQLDB, org.hsqldb.jdbc.JDBCDriver, jdbc:hsqldb:file:./data/test;hsqldb.default_table_type=cached;hsqldb.write_delay_millis=1000;shutdown=true, sa db3 = Derby, org.apache.derby.jdbc.AutoloadedDriver, jdbc:derby:data/derby;create=true, sa, sa +db9 = SQLite, org.sqlite.JDBC, jdbc:sqlite:data/testSQLite.db, sa, sa db4 = H2 (C/S), org.h2.Driver, jdbc:h2:tcp://localhost/./data/testServer, sa, sa db5 = HSQLDB (C/S), org.hsqldb.jdbcDriver, jdbc:hsqldb:hsql://localhost/xdb, sa @@ -15,12 +16,11 @@ db6 = Derby (C/S), org.apache.derby.jdbc.ClientDriver, jdbc:derby://localhost/da db7 = PG (C/S), org.postgresql.Driver, jdbc:postgresql://localhost:5432/test, sa, sa db8 = MySQL (C/S), com.mysql.cj.jdbc.Driver, jdbc:mysql://localhost:3306/test, sa, sa -#db9 = MSSQLServer, com.microsoft.jdbc.sqlserver.SQLServerDriver, jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=test, test, test -#db9 = Oracle, oracle.jdbc.driver.OracleDriver, jdbc:oracle:thin:@localhost:1521:XE, client, client -#db9 = Firebird, org.firebirdsql.jdbc.FBDriver, jdbc:firebirdsql:localhost:test?encoding=UTF8, sa, sa -#db9 = DB2, COM.ibm.db2.jdbc.net.DB2Driver, jdbc:db2://localhost/test, test, test -#db9 = OneDollarDB, in.co.daffodil.db.jdbc.DaffodilDBDriver, jdbc:daffodilDB_embedded:school;path=C:/temp;create=true, sa -#db9 = SQLite, org.sqlite.JDBC, jdbc:sqlite:data/testSQLite.db, sa, sa +#db10 = MSSQLServer, com.microsoft.jdbc.sqlserver.SQLServerDriver, jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=test, test, test +#db10 = Oracle, oracle.jdbc.driver.OracleDriver, jdbc:oracle:thin:@localhost:1521:XE, client, client +#db10 = Firebird, org.firebirdsql.jdbc.FBDriver, jdbc:firebirdsql:localhost:test?encoding=UTF8, sa, sa +#db10 = DB2, COM.ibm.db2.jdbc.net.DB2Driver, jdbc:db2://localhost/test, test, test +#db10 = OneDollarDB, in.co.daffodil.db.jdbc.DaffodilDBDriver, jdbc:daffodilDB_embedded:school;path=C:/temp;create=true, sa db11 = H2 (mem), org.h2.Driver, jdbc:h2:mem:test;LOCK_MODE=0, sa, sa db12 = HSQLDB (mem), org.hsqldb.jdbcDriver, jdbc:hsqldb:mem:data/test;hsqldb.tx=mvcc;shutdown=true, sa diff --git a/h2/src/tools/org/h2/build/Build.java b/h2/src/tools/org/h2/build/Build.java index 21c3ea9719..c66b83d160 100644 --- a/h2/src/tools/org/h2/build/Build.java +++ b/h2/src/tools/org/h2/build/Build.java @@ -65,6 +65,8 @@ public class Build extends BuildBase { private static final String APIGUARDIAN_VERSION = "1.1.0"; + private static final String SQLITE_VERSION = "3.36.0.3"; + private boolean filesMissing; /** @@ -101,6 +103,8 @@ public void benchmark() { downloadUsingMaven("ext/mysql-connector-java-" + MYSQL_CONNECTOR_VERSION + ".jar", "mysql", "mysql-connector-java", MYSQL_CONNECTOR_VERSION, "f1da9f10a3de6348725a413304aab6d0aa04f923"); + downloadUsingMaven("ext/sqlite-" + SQLITE_VERSION + ".jar", + "org.xerial", "sqlite-jdbc", SQLITE_VERSION, "7fa71c4dfab806490cb909714fb41373ec552c29"); compile(); String cp = "temp" + @@ -111,7 +115,8 @@ public void benchmark() { File.pathSeparator + "ext/derbynet-" + DERBY_VERSION + ".jar" + // File.pathSeparator + "ext/derbyshared-" + DERBY_VERSION + ".jar" + File.pathSeparator + "ext/postgresql-" + PGJDBC_VERSION + ".jar" + - File.pathSeparator + "ext/mysql-connector-java-" + MYSQL_CONNECTOR_VERSION + ".jar"; + File.pathSeparator + "ext/mysql-connector-java-" + MYSQL_CONNECTOR_VERSION + ".jar" + + File.pathSeparator + "ext/sqlite-" + SQLITE_VERSION + ".jar"; StringList args = args("-Xmx128m", "-cp", cp, "-Dderby.system.durability=test", "org.h2.test.bench.TestPerformance"); execJava(args.plus("-init", "-db", "1")); @@ -122,6 +127,8 @@ public void benchmark() { execJava(args.plus("-db", "6")); execJava(args.plus("-db", "7")); execJava(args.plus("-db", "8", "-out", "ps.html")); + // Disable SQLite because it doesn't work with multi-threaded benchmark, BenchB + // execJava(args.plus("-db", "9")); } /** From 97ee35f8462bfce8cafd156b5ce7522203d374f5 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sun, 3 Apr 2022 17:29:47 -0400 Subject: [PATCH 39/93] In-memory mode: MVStore.removeMap() fail to drop MVMap object --- h2/src/main/org/h2/mvstore/MVMap.java | 8 ++++++-- h2/src/main/org/h2/mvstore/MVStore.java | 13 +++++++++++++ h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java | 4 +++- h2/src/main/org/h2/mvstore/tx/Transaction.java | 2 +- h2/src/main/org/h2/mvstore/tx/TransactionStore.java | 2 +- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/MVMap.java b/h2/src/main/org/h2/mvstore/MVMap.java index d1de1f181b..89dd588454 100644 --- a/h2/src/main/org/h2/mvstore/MVMap.java +++ b/h2/src/main/org/h2/mvstore/MVMap.java @@ -480,7 +480,9 @@ RootReference clearIt() { continue; } } - store.registerUnsavedMemory(rootPage.removeAllRecursive(version)); + if (isPersistent()) { + store.registerUnsavedMemory(rootPage.removeAllRecursive(version)); + } rootPage = emptyRootPage; return rootReference; } finally { @@ -1870,7 +1872,9 @@ public V operate(K key, V value, DecisionMaker decisionMaker) { continue; } } - store.registerUnsavedMemory(unsavedMemoryHolder.value + tip.processRemovalInfo(version)); + if (isPersistent()) { + store.registerUnsavedMemory(unsavedMemoryHolder.value + tip.processRemovalInfo(version)); + } return result; } finally { if(locked) { diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 318586ebc7..3a79603459 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -2732,6 +2732,14 @@ public void setRetentionTime(int ms) { this.retentionTime = ms; } + /** + * Indicates whether store versions are rolling. + * @return true if versions are rolling, false otherwise + */ + public boolean isVersioningRequired() { + return fileStore != null || versionsToKeep > 0; + } + /** * How many versions to retain for in-memory stores. If not set, 5 old * versions are retained. @@ -3160,6 +3168,11 @@ public void removeMap(MVMap map) { if (meta.remove(DataUtils.META_NAME + name) != null) { markMetaChanged(); } + // normally actual map removal is delayed, up until this current version go out os scope, + // but for in-memory case, when versions rolling is turned off, do it now + if (!isVersioningRequired()) { + maps.remove(id); + } } finally { storeLock.unlock(); } diff --git a/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java index 4b8a7a60c1..b856e720b5 100644 --- a/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java +++ b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java @@ -169,7 +169,9 @@ public V operate(Spatial key, V value, DecisionMaker decisionMaker) { unsavedMemory += page.removePage(version); } } - store.registerUnsavedMemory(unsavedMemory); + if (isPersistent()) { + store.registerUnsavedMemory(unsavedMemory); + } } finally { unlockRoot(p); } diff --git a/h2/src/main/org/h2/mvstore/tx/Transaction.java b/h2/src/main/org/h2/mvstore/tx/Transaction.java index 892bf4ef79..d7d8516c3f 100644 --- a/h2/src/main/org/h2/mvstore/tx/Transaction.java +++ b/h2/src/main/org/h2/mvstore/tx/Transaction.java @@ -330,7 +330,7 @@ public boolean allowNonRepeatableRead() { @SuppressWarnings({"unchecked","rawtypes"}) public void markStatementStart(HashSet>> maps) { markStatementEnd(); - if (txCounter == null) { + if (txCounter == null && store.store.isVersioningRequired()) { txCounter = store.store.registerVersionUsage(); } diff --git a/h2/src/main/org/h2/mvstore/tx/TransactionStore.java b/h2/src/main/org/h2/mvstore/tx/TransactionStore.java index bd4d43cdd9..dcaf2c6dba 100644 --- a/h2/src/main/org/h2/mvstore/tx/TransactionStore.java +++ b/h2/src/main/org/h2/mvstore/tx/TransactionStore.java @@ -628,7 +628,7 @@ void endTransaction(Transaction t, boolean hasChanges) { preparedTransactions.remove(txId); } - if (store.getFileStore() != null) { + if (store.isVersioningRequired()) { if (wasStored || store.getAutoCommitDelay() == 0) { store.commit(); } else { From 79ee067c281bf1b84ace1c0d225f92d6fe7102f3 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 4 Apr 2022 22:16:23 -0400 Subject: [PATCH 40/93] SHUTDOWN DEFRAG: carry over store current version --- h2/src/main/org/h2/mvstore/MVStore.java | 5 +++++ h2/src/main/org/h2/mvstore/MVStoreTool.java | 1 + 2 files changed, 6 insertions(+) diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 318586ebc7..9101e5f99e 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -1131,6 +1131,11 @@ private boolean findLastChunkWithCompleteValidChunkSet(Chunk[] lastChunkCandidat return false; } + void adoptMetaFrom(MVStore source) { + currentVersion = source.currentVersion; + lastMapId.set(source.lastMapId.get()); + } + private void setLastChunk(Chunk last) { chunks.clear(); lastChunk = last; diff --git a/h2/src/main/org/h2/mvstore/MVStoreTool.java b/h2/src/main/org/h2/mvstore/MVStoreTool.java index ae7f5e4f37..ff7771e75b 100644 --- a/h2/src/main/org/h2/mvstore/MVStoreTool.java +++ b/h2/src/main/org/h2/mvstore/MVStoreTool.java @@ -508,6 +508,7 @@ public static void compact(String sourceFileName, String targetFileName, boolean * @param target the target store */ public static void compact(MVStore source, MVStore target) { + target.adoptMetaFrom(source); int autoCommitDelay = target.getAutoCommitDelay(); boolean reuseSpace = target.getReuseSpace(); try { From 405aa8734075504355643a9d608e387b3124823a Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Thu, 7 Apr 2022 23:51:04 -0400 Subject: [PATCH 41/93] break DbException dependency on Value to control the size of MVStore jar --- .../h2/expression/ArrayElementReference.java | 3 ++- .../org/h2/expression/FieldReference.java | 3 ++- .../h2/expression/function/ArrayFunction.java | 3 ++- .../h2/expression/function/BitFunction.java | 3 ++- .../expression/function/DateTimeFunction.java | 3 ++- .../h2/expression/function/MathFunction.java | 5 +++-- h2/src/main/org/h2/message/DbException.java | 19 ------------------- h2/src/main/org/h2/mvstore/MVStoreTool.java | 3 +-- h2/src/main/org/h2/mvstore/db/Store.java | 19 +++++++++++++++++++ 9 files changed, 33 insertions(+), 28 deletions(-) diff --git a/h2/src/main/org/h2/expression/ArrayElementReference.java b/h2/src/main/org/h2/expression/ArrayElementReference.java index d02245e968..77f00ee421 100644 --- a/h2/src/main/org/h2/expression/ArrayElementReference.java +++ b/h2/src/main/org/h2/expression/ArrayElementReference.java @@ -8,6 +8,7 @@ import org.h2.api.ErrorCode; import org.h2.engine.SessionLocal; import org.h2.message.DbException; +import org.h2.mvstore.db.Store; import org.h2.value.TypeInfo; import org.h2.value.Value; import org.h2.value.ValueArray; @@ -59,7 +60,7 @@ public Expression optimize(SessionLocal session) { } break; default: - throw DbException.getInvalidExpressionTypeException("Array", left); + throw Store.getInvalidExpressionTypeException("Array", left); } return this; } diff --git a/h2/src/main/org/h2/expression/FieldReference.java b/h2/src/main/org/h2/expression/FieldReference.java index 248b937a55..64f33b8f52 100644 --- a/h2/src/main/org/h2/expression/FieldReference.java +++ b/h2/src/main/org/h2/expression/FieldReference.java @@ -10,6 +10,7 @@ import org.h2.api.ErrorCode; import org.h2.engine.SessionLocal; import org.h2.message.DbException; +import org.h2.mvstore.db.Store; import org.h2.util.ParserUtil; import org.h2.value.ExtTypeInfoRow; import org.h2.value.TypeInfo; @@ -50,7 +51,7 @@ public Expression optimize(SessionLocal session) { arg = arg.optimize(session); TypeInfo type = arg.getType(); if (type.getValueType() != Value.ROW) { - throw DbException.getInvalidExpressionTypeException("ROW", arg); + throw Store.getInvalidExpressionTypeException("ROW", arg); } int ordinal = 0; for (Entry entry : ((ExtTypeInfoRow) type.getExtTypeInfo()).getFields()) { diff --git a/h2/src/main/org/h2/expression/function/ArrayFunction.java b/h2/src/main/org/h2/expression/function/ArrayFunction.java index ff9798d0a4..1da2c408f4 100644 --- a/h2/src/main/org/h2/expression/function/ArrayFunction.java +++ b/h2/src/main/org/h2/expression/function/ArrayFunction.java @@ -13,6 +13,7 @@ import org.h2.expression.Expression; import org.h2.expression.TypedValueExpression; import org.h2.message.DbException; +import org.h2.mvstore.db.Store; import org.h2.value.TypeInfo; import org.h2.value.Value; import org.h2.value.ValueArray; @@ -152,7 +153,7 @@ public Expression optimize(SessionLocal session) { type = arg.getType(); int t = type.getValueType(); if (t != Value.ARRAY && t != Value.NULL) { - throw DbException.getInvalidExpressionTypeException(getName() + " array argument", arg); + throw Store.getInvalidExpressionTypeException(getName() + " array argument", arg); } break; } diff --git a/h2/src/main/org/h2/expression/function/BitFunction.java b/h2/src/main/org/h2/expression/function/BitFunction.java index 7172ff8b66..ac9cd23508 100644 --- a/h2/src/main/org/h2/expression/function/BitFunction.java +++ b/h2/src/main/org/h2/expression/function/BitFunction.java @@ -13,6 +13,7 @@ import org.h2.expression.aggregate.Aggregate; import org.h2.expression.aggregate.AggregateType; import org.h2.message.DbException; +import org.h2.mvstore.db.Store; import org.h2.util.Bits; import org.h2.value.DataType; import org.h2.value.TypeInfo; @@ -713,7 +714,7 @@ public static TypeInfo checkArgType(Expression arg) { case Value.BIGINT: return t; } - throw DbException.getInvalidExpressionTypeException("bit function argument", arg); + throw Store.getInvalidExpressionTypeException("bit function argument", arg); } @Override diff --git a/h2/src/main/org/h2/expression/function/DateTimeFunction.java b/h2/src/main/org/h2/expression/function/DateTimeFunction.java index 9f9c2add21..8dae5d514c 100644 --- a/h2/src/main/org/h2/expression/function/DateTimeFunction.java +++ b/h2/src/main/org/h2/expression/function/DateTimeFunction.java @@ -5,6 +5,7 @@ */ package org.h2.expression.function; +import org.h2.mvstore.db.Store; import static org.h2.util.DateTimeUtils.MILLIS_PER_DAY; import static org.h2.util.DateTimeUtils.NANOS_PER_DAY; import static org.h2.util.DateTimeUtils.NANOS_PER_HOUR; @@ -973,7 +974,7 @@ public Expression optimize(SessionLocal session) { int valueType = type.getValueType(); // TODO set scale when possible if (!DataType.isDateTimeType(valueType)) { - throw DbException.getInvalidExpressionTypeException("DATE_TRUNC datetime argument", left); + throw Store.getInvalidExpressionTypeException("DATE_TRUNC datetime argument", left); } else if (session.getMode().getEnum() == ModeEnum.PostgreSQL && valueType == Value.DATE) { type = TypeInfo.TYPE_TIMESTAMP_TZ; } diff --git a/h2/src/main/org/h2/expression/function/MathFunction.java b/h2/src/main/org/h2/expression/function/MathFunction.java index cfae2b4a9e..62731d6f0a 100644 --- a/h2/src/main/org/h2/expression/function/MathFunction.java +++ b/h2/src/main/org/h2/expression/function/MathFunction.java @@ -13,6 +13,7 @@ import org.h2.expression.Expression; import org.h2.expression.TypedValueExpression; import org.h2.message.DbException; +import org.h2.mvstore.db.Store; import org.h2.value.DataType; import org.h2.value.TypeInfo; import org.h2.value.Value; @@ -238,7 +239,7 @@ public Expression optimize(SessionLocal session) { if (valueType == Value.NULL) { commonType = TypeInfo.TYPE_BIGINT; } else if (!DataType.isNumericType(valueType)) { - throw DbException.getInvalidExpressionTypeException("MOD argument", + throw Store.getInvalidExpressionTypeException("MOD argument", DataType.isNumericType(left.getType().getValueType()) ? right : left); } type = DataType.isNumericType(divisorType.getValueType()) ? divisorType : commonType; @@ -378,7 +379,7 @@ private Expression optimizeRound(int scale, boolean scaleIsKnown, boolean scaleI break; } default: - throw DbException.getInvalidExpressionTypeException(getName() + " argument", left); + throw Store.getInvalidExpressionTypeException(getName() + " argument", left); } if (scaleIsNull) { return TypedValueExpression.get(ValueNull.INSTANCE, type); diff --git a/h2/src/main/org/h2/message/DbException.java b/h2/src/main/org/h2/message/DbException.java index a2549073df..756dcca55f 100644 --- a/h2/src/main/org/h2/message/DbException.java +++ b/h2/src/main/org/h2/message/DbException.java @@ -33,13 +33,9 @@ import org.h2.jdbc.JdbcSQLTimeoutException; import org.h2.jdbc.JdbcSQLTransactionRollbackException; import org.h2.jdbc.JdbcSQLTransientException; -import org.h2.util.HasSQL; import org.h2.util.SortedProperties; import org.h2.util.StringUtils; import org.h2.util.Utils; -import org.h2.value.TypeInfo; -import org.h2.value.Typed; -import org.h2.value.Value; /** * This exception wraps a checked exception. @@ -302,21 +298,6 @@ public static DbException getInvalidValueException(String param, Object value) { return get(INVALID_VALUE_2, value == null ? "null" : value.toString(), param); } - /** - * Gets a SQL exception meaning the type of expression is invalid or unknown. - * - * @param param the name of the parameter - * @param e the expression - * @return the exception - */ - public static DbException getInvalidExpressionTypeException(String param, Typed e) { - TypeInfo type = e.getType(); - if (type.getValueType() == Value.UNKNOWN) { - return get(UNKNOWN_DATA_TYPE_1, (e instanceof HasSQL ? (HasSQL) e : type).getTraceSQL()); - } - return get(INVALID_VALUE_2, type.getTraceSQL(), param); - } - /** * Gets a SQL exception meaning this value is too long. * diff --git a/h2/src/main/org/h2/mvstore/MVStoreTool.java b/h2/src/main/org/h2/mvstore/MVStoreTool.java index ff7771e75b..feefd0b93d 100644 --- a/h2/src/main/org/h2/mvstore/MVStoreTool.java +++ b/h2/src/main/org/h2/mvstore/MVStoreTool.java @@ -21,7 +21,6 @@ import org.h2.compress.CompressLZF; import org.h2.compress.Compressor; import org.h2.engine.Constants; -import org.h2.message.DbException; import org.h2.mvstore.tx.TransactionStore; import org.h2.mvstore.type.BasicDataType; import org.h2.mvstore.type.StringDataType; @@ -442,7 +441,7 @@ public static void compact(String fileName, boolean compress) { compact(fileName, tempName, compress); try { FileUtils.moveAtomicReplace(tempName, fileName); - } catch (DbException e) { + } catch (MVStoreException e) { String newName = fileName + Constants.SUFFIX_MV_STORE_NEW_FILE; FileUtils.delete(newName); FileUtils.move(tempName, newName); diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index 35c3a58207..d887aea276 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -31,8 +31,12 @@ import org.h2.store.InDoubtTransaction; import org.h2.store.fs.FileChannelInputStream; import org.h2.store.fs.FileUtils; +import org.h2.util.HasSQL; import org.h2.util.StringUtils; import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Typed; +import org.h2.value.Value; /** * A store with open tables. @@ -167,6 +171,21 @@ DbException convertMVStoreException(MVStoreException e) { } } + /** + * Gets a SQL exception meaning the type of expression is invalid or unknown. + * + * @param param the name of the parameter + * @param e the expression + * @return the exception + */ + public static DbException getInvalidExpressionTypeException(String param, Typed e) { + TypeInfo type = e.getType(); + if (type.getValueType() == Value.UNKNOWN) { + return DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, (e instanceof HasSQL ? (HasSQL) e : type).getTraceSQL()); + } + return DbException.get(ErrorCode.INVALID_VALUE_2, type.getTraceSQL(), param); + } + public MVStore getMvStore() { return mvStore; } From 02bff4503e8e85a64e5f152ca625ff993033de90 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Thu, 7 Apr 2022 23:59:38 -0400 Subject: [PATCH 42/93] dictionary, long lines, changelist --- h2/src/docsrc/html/changelog.html | 350 +----------------- .../main/org/h2/mvstore/db/LobStorageMap.java | 3 +- h2/src/main/org/h2/mvstore/db/Store.java | 3 +- h2/src/test/org/h2/test/db/TestLob.java | 5 +- h2/src/tools/org/h2/build/doc/dictionary.txt | 4 +- 5 files changed, 23 insertions(+), 342 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 8f6b322ac5..1e1ab80767 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,8 +21,20 @@

                    Change Log

                    Next Version (unreleased)

                      +
                    • Nothing yet ... +
                    • +
                    + +

                    Next Version 2.1.220 (2022-04-07)

                    +
                      +
                    • Issue #3471: Possibility of corruption after SHUTDOWN DEFRAG +
                    • +
                    • Issue #3473: DROP TABLE/INDEX causes memory leak +
                    • Issue #1808: Occasional NPE in concurrent update of LOB
                    • +
                    • Issue #3457: Increase max length of VAR* types +
                    • Issue #3439: Cannot use enum values in JSON without explicit casts
                    • Issue #3426: Regression: BIT(1) is not accepted in MySQL compatibility mode @@ -32,7 +44,7 @@

                      Next Version (unreleased)

                    • Issue #3414: H2 2.1.210: Query with Parameters throws NPE at org.h2.command.query.Query.getParameterValues(Query.java:449)
                    • -
                    • Issue #3413: Parser can't parse REFERENCES … NOT NULL +
                    • Issue #3413: Parser can't parse REFERENCES … NOT NULL
                    • Issue #3410: OOME with nested derived tables
                    • @@ -941,340 +953,4 @@

                      Version 2.0.202 (2021-11-25)

                    -

                    Version 1.4.200 (2019-10-14)

                    -
                      -
                    • PR #2168: Add non-standard SNAPSHOT isolation level to MVStore databases -
                    • -
                    • Issue #2165: Problem with secondary index on SERIALIZABLE isolation level -
                    • -
                    • Issue #2161: Remove undocumented PageStore-only FILE_LOCK=SERIALIZED -
                    • -
                    • PR #2155: Reduce code duplication -
                    • -
                    • Issue #1894: Confusing error message when database creation is disallowed -
                    • -
                    • Issue #2123: Random failures in TestTransactionStore -
                    • -
                    • Issue #2153: Different behavior in SET LOCK_TIMEOUT after 1.4.197 -
                    • -
                    • Issue #2150: Remove MULTI_THREADED setting and use multi-threaded MVStore and single-threaded PageStore backends -
                    • -
                    • Issue #216: Support READ UNCOMMITTED isolation level in MVStore mode -
                    • -
                    • Issue #678: Support REPEATABLE READ isolation level in MVStore mode -
                    • -
                    • Issue #174: Support SERIALIZABLE isolation level in MVStore mode -
                    • -
                    • Issue #2144: MVStore: read uncommitted doesn't see committed rows -
                    • -
                    • Issue #2142: CURRVAL / CURRENT VALUE FOR should return the value for the current session -
                    • -
                    • Issue #2136: ConstraintCheck concurrency regression -
                    • -
                    • PR #2137: Don't use SYSTEM_RANGE for SELECT without a FROM -
                    • -
                    • PR #2134: Assorted fixes and other changes in DateTimeUtils -
                    • -
                    • PR #2133: Optimize COUNT([ALL] constant) and other changes -
                    • -
                    • PR #2132: Typo and another bug in MVStore.readStoreHeader() -
                    • -
                    • Issue #2130: Group-sorted query returns invalid results with duplicate grouped columns in select list -
                    • -
                    • Issue #2120: Add IF EXISTS clause to column name in ALTER TABLE ALTER COLUMN statement -
                    • -
                    • Issue #521: Add support for the TIME WITH TIME ZONE data type -
                    • -
                    • PR #2127: Fix race condition / performance issue during snapshotting -
                    • -
                    • Issue #2124: MVStore build is broken -
                    • -
                    • PR #2122: Add support for LMT in time zones and fix large years in datetime values -
                    • -
                    • Issue #2067: Incorrect chunk space allocation during chunks movement -
                    • -
                    • PR #2066: Not so happy path - "four alternatives" implementation -
                    • -
                    • PR #2121: Reduce code duplication for datetime API with custom Calendar instances -
                    • -
                    • PR #2119: SQL: statement read consistency -
                    • -
                    • Issue #2116: Empty IN() operator should result in error (MSSQL) -
                    • -
                    • Issue #2036: CAST from TIME to TIMESTAMP returns incorrect result -
                    • -
                    • PR #2114: Assorted changes -
                    • -
                    • PR #2113: Add feature F411: Time zone specification -
                    • -
                    • PR #2111: CURRENT_CATALOG, SET CATALOG and other changes -
                    • -
                    • Issue #2109: IW date formatting does not produce proper output -
                    • -
                    • PR #2104: Fix ordinary grouping set with parentheses and empty grouping set in GROUP BY -
                    • -
                    • Issue #2103: Add QUOTE_IDENT() function to enquote an identifier in SQL -
                    • -
                    • Issue #2075: Add EXECUTE IMMEDIATE implementation -
                    • -
                    • PR #2101: Fix infinite loop in Schema.removeChildrenAndResources() -
                    • -
                    • Issue #2096: Convert LEFT and RIGHT to keywords and disallow comma before closing parenthesis -
                    • -
                    • PR #2098: Fix typos -
                    • -
                    • Issue #1305 / PR #2097: Remove unused and outdated website translation infrastructure -
                    • -
                    • PR #2093: CURRENT VALUE FOR and other sequence-related changes -
                    • -
                    • PR #2092: Allow to simulate usage of multiple catalogs by one connection -
                    • -
                    • PR #2091: Oracle mode now uses DECIMAL with NEXTVAL -
                    • -
                    • Issue #2088: Division by zero caused by evaluation of global conditions before local conditions -
                    • -
                    • Issue #2086: TCP_QUICKACK on server socket -
                    • -
                    • Issue #2073: TableLink should not pass queries to DatabaseMetaData.getColumns() -
                    • -
                    • Issue #2074: MySQL and MSSQLServer Mode: TRUNCATE TABLE should always RESTART IDENTITY -
                    • -
                    • Issue #2063: MySQL mode: "drop foreign key if exists" support -
                    • -
                    • PR #2061: Use VirtualTable as a base class for RangeTable -
                    • -
                    • PR #2059: Parse IN predicate with multiple subqueries correctly -
                    • -
                    • PR #2057: Fix TestCrashAPI failure with Statement.enquoteIdentifier() -
                    • -
                    • PR #2056: Happy path: speed up database opening -
                    • -
                    • Issue #2051: The website shows outdated information about the storage engine -
                    • -
                    • PR #2049: bugfix - mvstore data lost issue when partial write occurs -
                    • -
                    • PR #2047: File maintenance -
                    • -
                    • PR #2046: Recovery mode -
                    • -
                    • Issue #2044: setTransactionIsolation always call commit() even if transaction is auto-commit -
                    • -
                    • Issue #2042: Add possibility to specify generated columns for query in web console -
                    • -
                    • Issue #2040: INFORMATION_SCHEMA.SETTINGS contains irrelevant settings -
                    • -
                    • PR #2038: MVMap: lock reduction on updates -
                    • -
                    • PR #2037: Fix SYS_GUID, RAWTOHEX, and HEXTORAW in Oracle mode -
                    • -
                    • Issue #2016: ExpressionColumn.mapColumns() performance complexity is quadratic -
                    • -
                    • Issue #2028: Sporadic inconsistent state after concurrent UPDATE in 1.4.199 -
                    • -
                    • PR #2033: Assorted changes -
                    • -
                    • Issue #2025: Incorrect query result when (OFFSET + FETCH) > Integer.MAX_VALUE -
                    • -
                    • PR #2023: traverseDown() code deduplication -
                    • -
                    • PR #2022: Mvmap minor cleanup -
                    • -
                    • Issue #2020: Wrong implementation of IN predicate with subquery -
                    • -
                    • PR #2003: Change dead chunks determination algorithm -
                    • -
                    • Issue #2013: DECIMAL is casted to double in ROUND function -
                    • -
                    • PR #2011: ZonedDateTime and (INTERVAL / INTERVAL) -
                    • -
                    • Issue #1997: TestRandomSQL failure with ClassCastException -
                    • -
                    • Issue #2007: PostgreSQL compatibility mode: support ON CONFLICT DO NOTHING -
                    • -
                    • Issue #1927: Do not allow commit() when auto-commit is enabled -
                    • -
                    • PR #1998: Reduce TxCounter memory footprint -
                    • -
                    • PR #1999: Make RootReference lock re-entrant -
                    • -
                    • PR #2001: Test improvements, OOME elimination -
                    • -
                    • Issue #1995: Obscure condition in MVPrimaryIndex.extractPKFromRow() -
                    • -
                    • Issue #1975: Add client ip address to information_schema -
                    • -
                    • PR #1982: Hindi language translation added -
                    • -
                    • Issue #1985: Add thread number to TCP server thread names -
                    • -
                    • Do not allow empty password for management DB -
                    • -
                    • Issue #1978: getGeneratedKeys() can use the same rules as FINAL TABLE -
                    • -
                    • PR #1977: Change JSON literals and add support for compound character literals -
                    • -
                    • PR #1974: Use proleptic Gregorian calendar for datetime values -
                    • -
                    • Issue #1847: Add support for data change delta tables -
                    • -
                    • PR #1971: Add maximum cardinality parameter to ARRAY data type -
                    • -
                    • PR #1970: Switch from log map rename to "committed" marker log record -
                    • -
                    • PR #1969: Add unique predicate -
                    • -
                    • Issue #1963: Expression.addFilterConditions() with outer joins -
                    • -
                    • PR #1966: Add standard CURRENT_SCHEMA function -
                    • -
                    • PR #1964: Add Feature T571: Truth value tests -
                    • -
                    • PR #1962: Fix data types of optimized conditions -
                    • -
                    • PR #1961: Failure to open DB after improper shutdown -
                    • -
                    • Issue #1957: NullPointerException with DISTINCT and ORDER BY CASE -
                    • -
                    • PR #1956: Fix row value handling in the null predicate -
                    • -
                    • PR #1955: Add standard UNKNOWN literal -
                    • -
                    • Issue #1952: Connection.setSchema doesn't work with query cache -
                    • -
                    • PR #1951: Assorted changes -
                    • -
                    • PR #1950: Fix NULL handling in ARRAY_AGG -
                    • -
                    • PR #1949: Extract aggregate and window functions into own pages in documentation -
                    • -
                    • PR #1948: Add standard LOG() function with two arguments -
                    • -
                    • Issue #1935: Improve file locking on shared filesystems like SMB -
                    • -
                    • PR #1946: Reimplement table value constructor on top of Query -
                    • -
                    • PR #1945: Fix IN (SELECT UNION with OFFSET/FETCH) -
                    • -
                    • Issue #1942: MySQL Mode: convertInsertNullToZero should be turned off by default? -
                    • -
                    • Issue #1940: MySQL Mode: Modify column from NOT NULL to NULL syntax -
                    • -
                    • PR #1941: Extract OFFSET / FETCH handling from Select and SelectUnion to Query -
                    • -
                    • Issue #1938: Regression with CREATE OR REPLACE VIEW. Causes "Duplicate column name" exception. -
                    • -
                    • PR #1937: Get rid of FunctionCursorResultSet -
                    • -
                    • Issue #1932: Incoherence between DbSettings.mvStore and getSettings() -
                    • -
                    • PR #1931: Fix wildcard expansion for multiple schemas -
                    • -
                    • PR #1930: Move PageStore table engine into own package -
                    • -
                    • PR #1929: Initial implementation of type predicate and other changes -
                    • -
                    • PR #1926: Assorted improvements for BINARY data type -
                    • -
                    • Issue #1925: Support SQL Server binary literal syntax -
                    • -
                    • Issue #1918: MySQL: CREATE TABLE with both CHARSET and COMMENT failed -
                    • -
                    • Issue #1913: MySQL: auto_increment changing SQL not supported -
                    • -
                    • Issue #1585: The translate function on DB2 mode could have parameters order changed -
                    • -
                    • PR #1914: Change storage and network format of JSON to byte[] -
                    • -
                    • Issue #1911: Foreign key constraint does not prevent table being dropped -
                    • -
                    • PR #1909: Add JSON_OBJECTAGG and JSON_ARRAYAGG aggregate functions -
                    • -
                    • PR #1908: Cast VARCHAR to JSON properly and require FORMAT JSON in literals -
                    • -
                    • PR #1906: Add JSON_OBJECT and JSON_ARRAY functions -
                    • -
                    • Issue #1887: Infinite recursion in ConditionAndOr.java -
                    • -
                    • Issue #1903: MSSQLServer Mode - Support Update TOP(X) -
                    • -
                    • Issue #1900: Support SQLServer stored procedure execution syntax -
                    • -
                    • PR #1898: Add IS JSON predicate -
                    • -
                    • Issue #1896: MSSQLServer compatibility mode - GETDATE() incorrectly omits time -
                    • -
                    • PR #1895: Add standard array concatenation operation -
                    • -
                    • Issue #1892: Window aggregate functions return incorrect result without window ordering and with ROWS unit -
                    • -
                    • Issue #1890: ArrayIndexOutOfBoundsException in MVSortedTempResult.getKey -
                    • -
                    • Issue #308: Mode MySQL and LAST_INSERT_ID with argument -
                    • -
                    • Issue #1883: Suspicious code in Session.getLocks() -
                    • -
                    • Issue #1878: OPTIMIZE_REUSE_RESULTS causes incorrect result after rollback since 1.4.198 -
                    • -
                    • PR #1880: Collation names like CHARSET_* recognition -
                    • -
                    • Issue #1844: MySQL Compatibility: create table error when primary key has comment -
                    • -
                    • PR #1873: Concurrency in database metadata -
                    • -
                    • Issue #1864: Failing to format NotSerializableException corrupting the database -
                    • -
                    • PR #1868: add more checking to TestFileLock -
                    • -
                    • Issue #1819: Trace.db file exceed file size limit (64MB) -
                    • -
                    • Issue #1861: Use COALESCE in named columns join for some data types -
                    • -
                    • PR #1860: Additional fix for deadlock on shutdown (exclusively in PageStore mode) -
                    • -
                    • Issue #1855: Wrong qualified asterisked projections in named column join -
                    • -
                    • Issue #1854: Wrong asterisked projection and result in named column right outer join -
                    • -
                    • Issue #1852: Named column joins doesn't work with the VALUES constructor and derived column lists -
                    • -
                    • Issue #1851: Wrong asterisked projection in named column joins -
                    • -
                    • PR #1850: Duplicate map identifiers -
                    • -
                    • PR #1849: Reimplement MVStore.findOldChunks() with PriorityQueue -
                    • -
                    • PR #1848: Reimplement MVStore.findChunksToMove() with PriorityQueue -
                    • -
                    • Issue #1843: Named columns join syntax is not supported -
                    • -
                    • Issue #1841: Deadlock during concurrent shutdown attempts with 1.4.199 -
                    • -
                    • Issue #1834: NUMERIC does not preserve its scale for some values -
                    • -
                    • PR #1838: Implement conversion from JSON to GEOMETRY -
                    • -
                    • PR #1837: Implement conversion from GEOMETRY to JSON -
                    • -
                    • PR #1836: Add LSHIFT and RSHIFT function -
                    • -
                    • PR #1833: Add BITNOT function -
                    • -
                    • PR #1832: JSON validation and normalization -
                    • -
                    • PR #1829: MVStore chunks occupancy rate calculation fixes -
                    • -
                    • PR #1828: Basis for implementation of SQL/JSON standard -
                    • -
                    • PR #1827: Add support for Lucene 8.0.0 -
                    • -
                    • Issue #1820: Performance problem on commit -
                    • -
                    • Issue #1822: Use https:// in h2database.com hyperlinks -
                    • -
                    • PR #1817: Assorted minor changes in documentation and other places -
                    • -
                    • PR #1812: An IllegalStateException that wraps EOFException is thrown when partial writes happens -
                    • -
                    -
          diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index cd62c1cba8..729b0df25d 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -404,7 +404,8 @@ public void cleanup(long oldestVersionToKeep) { MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { LobRemovalInfo lobRemovalInfo; - while ((lobRemovalInfo = pendingLobRemovals.poll()) != null && lobRemovalInfo.version < oldestVersionToKeep) { + while ((lobRemovalInfo = pendingLobRemovals.poll()) != null && + lobRemovalInfo.version < oldestVersionToKeep) { doRemoveLob(lobRemovalInfo.mapId, lobRemovalInfo.lobId); } if (lobRemovalInfo != null) { diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index d887aea276..4f051b2e0d 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -181,7 +181,8 @@ DbException convertMVStoreException(MVStoreException e) { public static DbException getInvalidExpressionTypeException(String param, Typed e) { TypeInfo type = e.getType(); if (type.getValueType() == Value.UNKNOWN) { - return DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, (e instanceof HasSQL ? (HasSQL) e : type).getTraceSQL()); + return DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, + (e instanceof HasSQL ? (HasSQL) e : type).getTraceSQL()); } return DbException.get(ErrorCode.INVALID_VALUE_2, type.getTraceSQL(), param); } diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java index 076c98db4a..293f40c99a 100644 --- a/h2/src/test/org/h2/test/db/TestLob.java +++ b/h2/src/test/org/h2/test/db/TestLob.java @@ -1581,7 +1581,7 @@ private void testLimitsLarge(byte[] b, String s, ValueLob v) throws IOException assertEquals(s, IOUtils.readStringAndClose(v.getReader(), -1)); } } - + public void testConcurrentSelectAndUpdate() throws SQLException, InterruptedException { deleteDb("lob"); try (JdbcConnection conn1 = (JdbcConnection) getConnection("lob")) { @@ -1605,7 +1605,8 @@ public void testConcurrentSelectAndUpdate() throws SQLException, InterruptedExce try { String update = "update t1 set ver = ver + 1 where id = 1"; try (PreparedStatement ps = conn2.prepareStatement(update)) { - while (!Thread.currentThread().isInterrupted() && System.nanoTime() - startTimeNs < 10_000_000_000L) { + while (!Thread.currentThread().isInterrupted() && + System.nanoTime() - startTimeNs < 10_000_000_000L) { ps.executeUpdate(); } } diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt index 289fe62130..32bf11dd5e 100644 --- a/h2/src/tools/org/h2/build/doc/dictionary.txt +++ b/h2/src/tools/org/h2/build/doc/dictionary.txt @@ -847,4 +847,6 @@ 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 supplementary happier hasn officially rnrn sonatype abandoned ldt odt +abandoned accidental approximately cited competitive configuring drastically happier hasn interactions journal +journaling ldt occasional odt officially pragma ration recognising rnrn rough seemed sonatype supplementary subtree ver +wal wbr worse xerial From eb99ab0c43c3070e0651b8bb2c9a1994b2d23bb5 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Fri, 8 Apr 2022 00:09:52 -0400 Subject: [PATCH 43/93] Version advancement --- README.md | 2 +- h2/pom.xml | 2 +- h2/src/docsrc/html/changelog.html | 2 +- h2/src/docsrc/html/download-archive.html | 4 ++++ h2/src/main/org/h2/engine/Constants.java | 6 +++--- h2/src/test/org/h2/samples/newsfeed.sql | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 70de378686..a26b652202 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ More information: https://h2database.com com.h2database h2 - 2.1.210 + 2.1.220 ``` diff --git a/h2/pom.xml b/h2/pom.xml index 1bc18cf054..81cbd730eb 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -4,7 +4,7 @@ com.h2database h2 - 2.2.219-SNAPSHOT + 2.2.220 jar H2 Database Engine https://h2database.com diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 1e1ab80767..e9972754a7 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -25,7 +25,7 @@

          Next Version (unreleased)

        -

        Next Version 2.1.220 (2022-04-07)

        +

        Next Version 2.1.220 (2022-04-09)

        • Issue #3471: Possibility of corruption after SHUTDOWN DEFRAG
        • diff --git a/h2/src/docsrc/html/download-archive.html b/h2/src/docsrc/html/download-archive.html index 09b4b11a50..f3a06d8357 100644 --- a/h2/src/docsrc/html/download-archive.html +++ b/h2/src/docsrc/html/download-archive.html @@ -28,6 +28,10 @@

          Distribution

          + + + + diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index becd10a21a..aa40700d3c 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -15,18 +15,18 @@ public class Constants { /** * The build date is updated for each public release. */ - public static final String BUILD_DATE = "2022-01-17"; + public static final String BUILD_DATE = "2022-04-09"; /** * Sequential version number. Even numbers are used for official releases, * odd numbers are used for development builds. */ - public static final int BUILD_ID = 219; + public static final int BUILD_ID = 220; /** * Whether this is a snapshot version. */ - public static final boolean BUILD_SNAPSHOT = true; + public static final boolean BUILD_SNAPSHOT = false; /** * If H2 is compiled to be included in a product, this should be set to diff --git a/h2/src/test/org/h2/samples/newsfeed.sql b/h2/src/test/org/h2/samples/newsfeed.sql index 82c483be5c..15684baa17 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 +(155, '2.1.220', '2022-04-09'), (154, '2.1.210', '2022-01-17'), (153, '2.0.206', '2022-01-04'), (152, '2.0.204', '2021-12-21'), @@ -19,8 +20,7 @@ INSERT INTO VERSION VALUES (145, '1.4.195', '2017-04-23'), (144, '1.4.194', '2017-03-10'), (143, '1.4.193', '2016-10-31'), -(142, '1.4.192', '2016-05-26'), -(141, '1.4.191', '2016-01-21'); +(142, '1.4.192', '2016-05-26'); CREATE TABLE CHANNEL(TITLE VARCHAR, LINK VARCHAR, DESC VARCHAR, LANGUAGE VARCHAR, PUB TIMESTAMP, LAST TIMESTAMP, AUTHOR VARCHAR); From 5657be7dec66f0979ba9ae2a6fc491ada5ddbe03 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 9 Apr 2022 10:22:05 +0800 Subject: [PATCH 44/93] Fix building of documentation --- h2/src/docsrc/html/changelog.html | 2 +- h2/src/docsrc/html/performance.html | 2 +- h2/src/main/org/h2/mvstore/db/LobStorageMap.java | 3 ++- h2/src/test/org/h2/test/db/TestLob.java | 5 +++-- h2/src/tools/org/h2/build/doc/dictionary.txt | 2 ++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 8f6b322ac5..a3a2d3cacb 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -32,7 +32,7 @@

          Next Version (unreleased)

        • Issue #3414: H2 2.1.210: Query with Parameters throws NPE at org.h2.command.query.Query.getParameterValues(Query.java:449)
        • -
        • Issue #3413: Parser can't parse REFERENCES … NOT NULL +
        • Issue #3413: Parser can't parse REFERENCES … NOT NULL
        • Issue #3410: OOME with nested derived tables
        • diff --git a/h2/src/docsrc/html/performance.html b/h2/src/docsrc/html/performance.html index 0643b3cc1f..0bb71c8ea7 100644 --- a/h2/src/docsrc/html/performance.html +++ b/h2/src/docsrc/html/performance.html @@ -161,7 +161,7 @@

          SQLite

          SQLite 3.36.0.3, configured to use WAL and with synchronous=NORMAL was tested in a separate, less reliable run. A rough estimate is that SQLite performs approximately 2-5x worse in the simple benchmarks, -which perform simple work in the database, resulting in a low work-per-transaction ration. SQLite becomes competitive as +which perform simple work in the database, resulting in a low work-per-transaction ratio. SQLite becomes competitive as the complexity of the database interactions increases. The results seemed to vary drastically across machine, and more reliable results should be obtained. Benchmark on your production hardware.

          diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index cd62c1cba8..948ec39886 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -404,7 +404,8 @@ public void cleanup(long oldestVersionToKeep) { MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { LobRemovalInfo lobRemovalInfo; - while ((lobRemovalInfo = pendingLobRemovals.poll()) != null && lobRemovalInfo.version < oldestVersionToKeep) { + while ((lobRemovalInfo = pendingLobRemovals.poll()) != null + && lobRemovalInfo.version < oldestVersionToKeep) { doRemoveLob(lobRemovalInfo.mapId, lobRemovalInfo.lobId); } if (lobRemovalInfo != null) { diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java index 076c98db4a..0c660fc789 100644 --- a/h2/src/test/org/h2/test/db/TestLob.java +++ b/h2/src/test/org/h2/test/db/TestLob.java @@ -1581,7 +1581,7 @@ private void testLimitsLarge(byte[] b, String s, ValueLob v) throws IOException assertEquals(s, IOUtils.readStringAndClose(v.getReader(), -1)); } } - + public void testConcurrentSelectAndUpdate() throws SQLException, InterruptedException { deleteDb("lob"); try (JdbcConnection conn1 = (JdbcConnection) getConnection("lob")) { @@ -1605,7 +1605,8 @@ public void testConcurrentSelectAndUpdate() throws SQLException, InterruptedExce try { String update = "update t1 set ver = ver + 1 where id = 1"; try (PreparedStatement ps = conn2.prepareStatement(update)) { - while (!Thread.currentThread().isInterrupted() && System.nanoTime() - startTimeNs < 10_000_000_000L) { + while (!Thread.currentThread().isInterrupted() + && System.nanoTime() - startTimeNs < 10_000_000_000L) { ps.executeUpdate(); } } diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt index 289fe62130..906b5aa4f7 100644 --- a/h2/src/tools/org/h2/build/doc/dictionary.txt +++ b/h2/src/tools/org/h2/build/doc/dictionary.txt @@ -848,3 +848,5 @@ orientation eternal consideration erased fedc npgsql powers fffd uencode ampersa 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 supplementary happier hasn officially rnrn sonatype abandoned ldt odt +ver wal rough configuring xerial seemed journaling cited occasional worse pragma interactions journal approximately +drastically competitive From a71a66e9f240a34173ef26c4221b5aa7cdb6c121 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 9 Apr 2022 10:22:47 +0800 Subject: [PATCH 45/93] Add support for standard interval literals with precision --- h2/src/main/org/h2/command/Parser.java | 78 ++----------------- h2/src/main/org/h2/res/help.csv | 47 +++++++---- .../h2/test/scripts/datatypes/interval.sql | 3 + 3 files changed, 44 insertions(+), 84 deletions(-) diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 02d7eab980..cc9ce677c7 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -5491,78 +5491,14 @@ private Expression readInterval() { } String s = token.value(session).getString(); read(); - IntervalQualifier qualifier; - switch (currentTokenType) { - case YEAR: - read(); - if (readIf(TO)) { - read(MONTH); - qualifier = IntervalQualifier.YEAR_TO_MONTH; - } else { - qualifier = IntervalQualifier.YEAR; - } - break; - case MONTH: - read(); - qualifier = IntervalQualifier.MONTH; - break; - case DAY: - read(); - if (readIf(TO)) { - switch (currentTokenType) { - case HOUR: - qualifier = IntervalQualifier.DAY_TO_HOUR; - break; - case MINUTE: - qualifier = IntervalQualifier.DAY_TO_MINUTE; - break; - case SECOND: - qualifier = IntervalQualifier.DAY_TO_SECOND; - break; - default: - throw intervalDayError(); - } - read(); - } else { - qualifier = IntervalQualifier.DAY; - } - break; - case HOUR: - read(); - if (readIf(TO)) { - switch (currentTokenType) { - case MINUTE: - qualifier = IntervalQualifier.HOUR_TO_MINUTE; - break; - case SECOND: - qualifier = IntervalQualifier.HOUR_TO_SECOND; - break; - default: - throw intervalHourError(); - } - read(); - } else { - qualifier = IntervalQualifier.HOUR; - } - break; - case MINUTE: - read(); - if (readIf(TO)) { - read(SECOND); - qualifier = IntervalQualifier.MINUTE_TO_SECOND; - } else { - qualifier = IntervalQualifier.MINUTE; - } - break; - case SECOND: - read(); - qualifier = IntervalQualifier.SECOND; - break; - default: - throw intervalQualifierError(); - } + TypeInfo typeInfo = readIntervalQualifier(); try { - return ValueExpression.get(IntervalUtils.parseInterval(qualifier, negative, s)); + ValueInterval interval = IntervalUtils.parseInterval( + IntervalQualifier.valueOf(typeInfo.getValueType() - Value.INTERVAL_YEAR), negative, s); + if (typeInfo.getDeclaredPrecision() != -1L || typeInfo.getDeclaredScale() != -1) { + return TypedValueExpression.get(interval.castTo(typeInfo, session), typeInfo); + } + return ValueExpression.get(interval); } catch (Exception e) { throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "INTERVAL", s); } diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index b5ef3008b5..381726fb6a 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -2400,105 +2400,126 @@ INTERVAL '1-2' YEAR TO MONTH " "Literals","INTERVAL YEAR"," -INTERVAL [-|+] '[-|+]yearInt' YEAR +INTERVAL [-|+] '[-|+]yearInt' YEAR [ ( precisionInt ) ] "," An INTERVAL YEAR literal. +If precision is specified it should be from 1 to 18. "," INTERVAL '10' YEAR " "Literals","INTERVAL MONTH"," -INTERVAL [-|+] '[-|+]monthInt' MONTH +INTERVAL [-|+] '[-|+]monthInt' MONTH [ ( precisionInt ) ] "," An INTERVAL MONTH literal. +If precision is specified it should be from 1 to 18. "," INTERVAL '10' MONTH " "Literals","INTERVAL DAY"," -INTERVAL [-|+] '[-|+]dayInt' DAY +INTERVAL [-|+] '[-|+]dayInt' DAY [ ( precisionInt ) ] "," An INTERVAL DAY literal. +If precision is specified it should be from 1 to 18. "," INTERVAL '10' DAY " "Literals","INTERVAL HOUR"," -INTERVAL [-|+] '[-|+]hourInt' HOUR +INTERVAL [-|+] '[-|+]hourInt' HOUR [ ( precisionInt ) ] "," An INTERVAL HOUR literal. +If precision is specified it should be from 1 to 18. "," INTERVAL '10' HOUR " "Literals","INTERVAL MINUTE"," -INTERVAL [-|+] '[-|+]minuteInt' MINUTE +INTERVAL [-|+] '[-|+]minuteInt' MINUTE [ ( precisionInt ) ] "," An INTERVAL MINUTE literal. +If precision is specified it should be from 1 to 18. "," INTERVAL '10' MINUTE " "Literals","INTERVAL SECOND"," -INTERVAL [-|+] '[-|+]secondInt[.nnnnnnnnn]' SECOND +INTERVAL [-|+] '[-|+]secondInt[.nnnnnnnnn]' +SECOND [ ( precisionInt [, fractionalPrecisionInt ] ) ] "," An INTERVAL SECOND literal. +If precision is specified it should be from 1 to 18. +If fractional seconds precision is specified it should be from 0 to 9. "," INTERVAL '10.123' SECOND " "Literals","INTERVAL YEAR TO MONTH"," -INTERVAL [-|+] '[-|+]yearInt-monthInt' YEAR TO MONTH +INTERVAL [-|+] '[-|+]yearInt-monthInt' YEAR [ ( precisionInt ) ] TO MONTH "," An INTERVAL YEAR TO MONTH literal. +If leading field precision is specified it should be from 1 to 18. "," INTERVAL '1-6' YEAR TO MONTH " "Literals","INTERVAL DAY TO HOUR"," -INTERVAL [-|+] '[-|+]dayInt hoursInt' DAY TO HOUR +INTERVAL [-|+] '[-|+]dayInt hoursInt' DAY [ ( precisionInt ) ] TO HOUR "," An INTERVAL DAY TO HOUR literal. +If leading field precision is specified it should be from 1 to 18. "," INTERVAL '10 11' DAY TO HOUR " "Literals","INTERVAL DAY TO MINUTE"," -INTERVAL [-|+] '[-|+]dayInt hh:mm' DAY TO MINUTE +INTERVAL [-|+] '[-|+]dayInt hh:mm' DAY [ ( precisionInt ) ] TO MINUTE "," An INTERVAL DAY TO MINUTE literal. +If leading field precision is specified it should be from 1 to 18. "," INTERVAL '10 11:12' DAY TO MINUTE " "Literals","INTERVAL DAY TO SECOND"," -INTERVAL [-|+] '[-|+]dayInt hh:mm:ss[.nnnnnnnnn]' DAY TO SECOND +INTERVAL [-|+] '[-|+]dayInt hh:mm:ss[.nnnnnnnnn]' DAY [ ( precisionInt ) ] +TO SECOND [ ( fractionalPrecisionInt ) ] "," An INTERVAL DAY TO SECOND literal. +If leading field precision is specified it should be from 1 to 18. +If fractional seconds precision is specified it should be from 0 to 9. "," INTERVAL '10 11:12:13.123' DAY TO SECOND " "Literals","INTERVAL HOUR TO MINUTE"," -INTERVAL [-|+] '[-|+]hh:mm' HOUR TO MINUTE +INTERVAL [-|+] '[-|+]hh:mm' HOUR [ ( precisionInt ) ] TO MINUTE "," An INTERVAL HOUR TO MINUTE literal. +If leading field precision is specified it should be from 1 to 18. "," INTERVAL '10:11' HOUR TO MINUTE " "Literals","INTERVAL HOUR TO SECOND"," -INTERVAL [-|+] '[-|+]hh:mm:ss[.nnnnnnnnn]' HOUR TO SECOND +INTERVAL [-|+] '[-|+]hh:mm:ss[.nnnnnnnnn]' HOUR [ ( precisionInt ) ] +TO SECOND [ ( fractionalPrecisionInt ) ] "," An INTERVAL HOUR TO SECOND literal. +If leading field precision is specified it should be from 1 to 18. +If fractional seconds precision is specified it should be from 0 to 9. "," INTERVAL '10:11:12.123' HOUR TO SECOND " "Literals","INTERVAL MINUTE TO SECOND"," -INTERVAL [-|+] '[-|+]mm:ss[.nnnnnnnnn]' MINUTE TO SECOND +INTERVAL [-|+] '[-|+]mm:ss[.nnnnnnnnn]' MINUTE [ ( precisionInt ) ] +TO SECOND [ ( fractionalPrecisionInt ) ] "," An INTERVAL MINUTE TO SECOND literal. +If leading field precision is specified it should be from 1 to 18. +If fractional seconds precision is specified it should be from 0 to 9. "," INTERVAL '11:12.123' MINUTE TO SECOND " diff --git a/h2/src/test/org/h2/test/scripts/datatypes/interval.sql b/h2/src/test/org/h2/test/scripts/datatypes/interval.sql index 89b53900e5..b08bc3c3f5 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/interval.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/interval.sql @@ -1100,3 +1100,6 @@ SELECT T1, T2, (T1 - T2) YEAR TO MONTH, (T2 - T1) YEAR TO MONTH FROM (VALUES SELECT (DATE '2010-01-02' - DATE '2000-01-01') YEAR; >> INTERVAL '10' YEAR + +VALUES INTERVAL '100' YEAR(2); +> exception INVALID_DATETIME_CONSTANT_2 From dd68bfc52b7d3575a6a0cc836fb9c00957afc698 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 9 Apr 2022 10:53:32 +0800 Subject: [PATCH 46/93] Fix INFORMATION_SCHEMA.ROUTINES.EXTERNAL_NAME for aliases with source code --- .../org/h2/table/InformationSchemaTable.java | 5 ++-- .../org/h2/test/scripts/ddl/createAlias.sql | 30 ++++++++++++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/h2/src/main/org/h2/table/InformationSchemaTable.java b/h2/src/main/org/h2/table/InformationSchemaTable.java index 736d1f311d..c7ac425500 100644 --- a/h2/src/main/org/h2/table/InformationSchemaTable.java +++ b/h2/src/main/org/h2/table/InformationSchemaTable.java @@ -1975,10 +1975,11 @@ private void routines(SessionLocal session, ArrayList rows, String catalog) } else { routineType = "FUNCTION"; } + String javaClassName = alias.getJavaClassName(); routines(session, rows, catalog, mainSchemaName, collation, schemaName, name, name + '_' + (i + 1), routineType, admin ? alias.getSource() : null, - alias.getJavaClassName() + '.' + alias.getJavaMethodName(), typeInfo, - alias.isDeterministic(), alias.getComment()); + javaClassName != null ? javaClassName + '.' + alias.getJavaMethodName() : null, + typeInfo, alias.isDeterministic(), alias.getComment()); } } else { routines(session, rows, catalog, mainSchemaName, collation, schemaName, name, name, "AGGREGATE", diff --git a/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql b/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql index 3a4234e1f3..4176fb775c 100644 --- a/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql +++ b/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql @@ -33,30 +33,44 @@ SELECT MY_SQRT(-1.0) MS, SQRT(NULL) S; > NaN null > rows: 1 +CREATE ALIAS MY_SUM AS 'int sum(int a, int b) { return a + b; }'; +> ok + +CALL MY_SUM(1, 2); +>> 3 + SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; > SCRIPT -> ---------------------------------------------------------------- +> ---------------------------------------------------------------------------------- > CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; > CREATE FORCE ALIAS "PUBLIC"."MY_SQRT" FOR 'java.lang.Math.sqrt'; -> rows (ordered): 2 +> CREATE FORCE ALIAS "PUBLIC"."MY_SUM" AS 'int sum(int a, int b) { return a + b; }'; +> rows (ordered): 3 -SELECT SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DATA_TYPE, ROUTINE_BODY, EXTERNAL_NAME, EXTERNAL_LANGUAGE, +SELECT SPECIFIC_NAME, ROUTINE_NAME, ROUTINE_TYPE, DATA_TYPE, ROUTINE_BODY, ROUTINE_DEFINITION, + EXTERNAL_NAME, EXTERNAL_LANGUAGE, IS_DETERMINISTIC, REMARKS FROM INFORMATION_SCHEMA.ROUTINES; -> SPECIFIC_NAME ROUTINE_NAME ROUTINE_TYPE DATA_TYPE ROUTINE_BODY EXTERNAL_NAME EXTERNAL_LANGUAGE IS_DETERMINISTIC REMARKS -> ------------- ------------ ------------ ---------------- ------------ ------------------- ----------------- ---------------- ------- -> MY_SQRT_1 MY_SQRT FUNCTION DOUBLE PRECISION EXTERNAL java.lang.Math.sqrt JAVA NO null -> rows: 1 +> SPECIFIC_NAME ROUTINE_NAME ROUTINE_TYPE DATA_TYPE ROUTINE_BODY ROUTINE_DEFINITION EXTERNAL_NAME EXTERNAL_LANGUAGE IS_DETERMINISTIC REMARKS +> ------------- ------------ ------------ ---------------- ------------ --------------------------------------- ------------------- ----------------- ---------------- ------- +> MY_SQRT_1 MY_SQRT FUNCTION DOUBLE PRECISION EXTERNAL null java.lang.Math.sqrt JAVA NO null +> MY_SUM_1 MY_SUM FUNCTION INTEGER EXTERNAL int sum(int a, int b) { return a + b; } null JAVA NO null +> rows: 2 SELECT SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, DATA_TYPE, PARAMETER_DEFAULT FROM INFORMATION_SCHEMA.PARAMETERS; > SPECIFIC_NAME ORDINAL_POSITION PARAMETER_MODE IS_RESULT AS_LOCATOR PARAMETER_NAME DATA_TYPE PARAMETER_DEFAULT > ------------- ---------------- -------------- --------- ---------- -------------- ---------------- ----------------- > MY_SQRT_1 1 IN NO NO P1 DOUBLE PRECISION null -> rows: 1 +> MY_SUM_1 1 IN NO NO P1 INTEGER null +> MY_SUM_1 2 IN NO NO P2 INTEGER null +> rows: 3 DROP ALIAS MY_SQRT; > ok +DROP ALIAS MY_SUM; +> ok + CREATE SCHEMA TEST_SCHEMA; > ok From 3b8f9f1afb7f962d53921ca74be9b71814692cc8 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 9 Apr 2022 10:54:20 +0800 Subject: [PATCH 47/93] Update changelog --- h2/src/docsrc/html/changelog.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index a3a2d3cacb..cfe9e509ab 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,6 +21,18 @@

          Change Log

          Next Version (unreleased)

            +
          • PR #3481: Add support for standard interval literals with precision +
          • +
          • Issue #3471: TRUNCATE TABLE seems to corrupt the Database file only after SHUTDOWN DEFRAG +
          • +
          • Issue #3473: Possible memory leak in 2.1.210 +
          • +
          • PR #3464 / Issue #3457: increase max length of VAR* types +
          • +
          • PR #3460: fix bug in readStoreHeader() +
          • +
          • PR #3458: Add performance tests for SQLite +
          • Issue #1808: Occasional NPE in concurrent update of LOB
          • Issue #3439: Cannot use enum values in JSON without explicit casts From dacad17830ae1c7eb6b88195ee5f3c16c3883e13 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sat, 9 Apr 2022 00:43:53 -0400 Subject: [PATCH 48/93] Pre-release changes for 2.1.212 --- h2/pom.xml | 2 +- h2/src/docsrc/html/changelog.html | 3 +-- h2/src/docsrc/html/download-archive.html | 6 +++--- h2/src/docsrc/html/download.html | 10 +++++----- h2/src/test/org/h2/samples/newsfeed.sql | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/h2/pom.xml b/h2/pom.xml index 81cbd730eb..9ee85482e9 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -4,7 +4,7 @@ com.h2database h2 - 2.2.220 + 2.1.212 jar H2 Database Engine https://h2database.com diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 16796e18e4..2552c84e4f 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -47,8 +47,7 @@

            Next Version 2.1.212 (2022-04-09)

          • PR #3422: Allow combination of any geometry types with the same SRID
          • -
          • Issue #3414: H2 2.1.210: Query with Parameters throws NPE at -org.h2.command.query.Query.getParameterValues(Query.java:449) +
          • Issue #3414: H2 2.1.210: Query with Parameters throws NPE
          • Issue #3413: Parser can't parse REFERENCES … NOT NULL
          • diff --git a/h2/src/docsrc/html/download-archive.html b/h2/src/docsrc/html/download-archive.html index f3a06d8357..730287d723 100644 --- a/h2/src/docsrc/html/download-archive.html +++ b/h2/src/docsrc/html/download-archive.html @@ -28,9 +28,9 @@

            Distribution

          2.1.220Windows InstallerPlatform-Independent Zip
          2.1.210 Windows Installer Platform-Independent Zip
          - - - + + + diff --git a/h2/src/docsrc/html/download.html b/h2/src/docsrc/html/download.html index 768c2ea78c..a0f1a00f33 100644 --- a/h2/src/docsrc/html/download.html +++ b/h2/src/docsrc/html/download.html @@ -27,12 +27,12 @@

          Version ${version} (${versionDate})


          -

          Version 2.0.206 (2022-01-04)

          +

          Version 2.1.210 (2022-01-17)

          -Windows Installer -(SHA1 checksum: 982dff9c88412b00b3ced52b6870753e0133be07)
          -Platform-Independent Zip -(SHA1 checksum: 85d6d8f552661c2f8e1b86c10a12ab4bb6b0d29b)
          +Windows Installer +(SHA1 checksum: ff795bf6ccefd5950d5080b596d835d13206b325)
          +Platform-Independent Zip +(SHA1 checksum: 6ede99a0a987971557e878de4eddcb796d604323)

          Archive Downloads

          diff --git a/h2/src/test/org/h2/samples/newsfeed.sql b/h2/src/test/org/h2/samples/newsfeed.sql index 15684baa17..60f9a1060c 100644 --- a/h2/src/test/org/h2/samples/newsfeed.sql +++ b/h2/src/test/org/h2/samples/newsfeed.sql @@ -7,7 +7,7 @@ CREATE TABLE VERSION(ID INT PRIMARY KEY, VERSION VARCHAR, CREATED VARCHAR); INSERT INTO VERSION VALUES -(155, '2.1.220', '2022-04-09'), +(155, '2.1.212', '2022-04-09'), (154, '2.1.210', '2022-01-17'), (153, '2.0.206', '2022-01-04'), (152, '2.0.204', '2021-12-21'), From 783421446f79c81ccdf951196463f0fad469b073 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sat, 9 Apr 2022 00:54:27 -0400 Subject: [PATCH 49/93] Pre-release 212 indeed --- h2/src/main/org/h2/engine/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index aa40700d3c..05ff4b48c7 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -21,7 +21,7 @@ public class Constants { * Sequential version number. Even numbers are used for official releases, * odd numbers are used for development builds. */ - public static final int BUILD_ID = 220; + public static final int BUILD_ID = 212; /** * Whether this is a snapshot version. From 024b2b2298819808f6c12628588be36578bfc46e Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sat, 9 Apr 2022 00:58:12 -0400 Subject: [PATCH 50/93] Pre-release 212 indeed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a26b652202..4f2f2fa58c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ More information: https://h2database.com com.h2database h2 - 2.1.220 + 2.1.212 ``` From fd7122e9ec815601630134624a830c1c7ee3f528 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sat, 9 Apr 2022 01:10:11 -0400 Subject: [PATCH 51/93] Pre-release 2.1.212 indeed --- h2/src/main/org/h2/engine/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index 05ff4b48c7..d85c844e8c 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -78,7 +78,7 @@ public class Constants { /** * The minor version of this database. */ - public static final int VERSION_MINOR = 2; + public static final int VERSION_MINOR = 1; /** * The lock mode that means no locking is used at all. From 0b480739b51e264a369606e6cbdb77e09782cc20 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Sat, 9 Apr 2022 10:55:47 -0400 Subject: [PATCH 52/93] post-release version advancement --- h2/pom.xml | 2 +- h2/src/main/org/h2/engine/Constants.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/h2/pom.xml b/h2/pom.xml index 9ee85482e9..1bc18cf054 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -4,7 +4,7 @@ com.h2database h2 - 2.1.212 + 2.2.219-SNAPSHOT jar H2 Database Engine https://h2database.com diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index d85c844e8c..f69e3b2996 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -21,12 +21,12 @@ public class Constants { * Sequential version number. Even numbers are used for official releases, * odd numbers are used for development builds. */ - public static final int BUILD_ID = 212; + public static final int BUILD_ID = 219; /** * Whether this is a snapshot version. */ - public static final boolean BUILD_SNAPSHOT = false; + public static final boolean BUILD_SNAPSHOT = true; /** * If H2 is compiled to be included in a product, this should be set to @@ -78,7 +78,7 @@ public class Constants { /** * The minor version of this database. */ - public static final int VERSION_MINOR = 1; + public static final int VERSION_MINOR = 2; /** * The lock mode that means no locking is used at all. From 30ac6ad526752541bd6b763461050056bd7da2d0 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Wed, 20 Apr 2022 20:03:34 -0400 Subject: [PATCH 53/93] fix removal of global temp table with identity column --- h2/src/main/org/h2/engine/Database.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 6f49bec122..5228639ac1 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -1192,7 +1192,7 @@ private void closeImpl(boolean fromShutdownHook) { for (Schema schema : schemas.values()) { for (Table table : schema.getAllTablesAndViews(null)) { if (table.isGlobalTemporary()) { - table.removeChildrenAndResources(systemSession); + removeSchemaObject(systemSession, table); } else { table.close(systemSession); } @@ -1219,6 +1219,7 @@ private void closeImpl(boolean fromShutdownHook) { } } } catch (DbException e) { + e.printStackTrace(); trace.error(e, "close"); } tempFileDeleter.deleteAll(); From 8d520248ab68cf54e2ac4995fbd8f4a298f0006b Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Wed, 20 Apr 2022 20:45:52 -0400 Subject: [PATCH 54/93] move blob storage cleanup to a dedicated executor --- h2/src/main/org/h2/engine/Database.java | 35 ++---- h2/src/main/org/h2/mvstore/MVStore.java | 102 ++++-------------- .../main/org/h2/mvstore/db/LobStorageMap.java | 48 +++++++-- .../org/h2/store/LobStorageInterface.java | 5 + h2/src/main/org/h2/util/Utils.java | 51 +++++++++ 5 files changed, 123 insertions(+), 118 deletions(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 5228639ac1..358d1f3119 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -1181,9 +1181,6 @@ private void closeImpl(boolean fromShutdownHook) { closeAllSessionsExcept(null); } } - if (!this.isReadOnly()) { - removeOrphanedLobs(); - } } try { try { @@ -1217,23 +1214,16 @@ private void closeImpl(boolean fromShutdownHook) { meta.close(systemSession); systemSession.commit(true); } - } - } catch (DbException e) { - e.printStackTrace(); - trace.error(e, "close"); - } - tempFileDeleter.deleteAll(); - try { - if (lobSession != null) { - lobSession.close(); - lobSession = null; - } - if (systemSession != null) { + if (lobSession != null) { + lobSession.close(); + lobSession = null; + } systemSession.close(); systemSession = null; } + tempFileDeleter.deleteAll(); closeOpenFilesAndUnlock(); - } catch (DbException e) { + } catch (DbException | MVStoreException e) { trace.error(e, "close"); } trace.info("closed"); @@ -1254,23 +1244,12 @@ private void closeImpl(boolean fromShutdownHook) { } } - private void removeOrphanedLobs() { - // remove all session variables and temporary lobs - if (!persistent) { - return; - } - try { - lobStorage.removeAllForTable(LobStorageFrontend.TABLE_ID_SESSION_VARIABLE); - } catch (DbException e) { - trace.error(e, "close"); - } - } - /** * Close all open files and unlock the database. */ private synchronized void closeOpenFilesAndUnlock() { try { + lobStorage.close(); if (!store.getMvStore().isClosed()) { if (compactMode == CommandInterface.SHUTDOWN_IMMEDIATELY) { store.closeImmediately(); diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index 61342b3289..fc2776471b 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.LongConsumer; import java.util.function.Predicate; import java.util.function.Supplier; import org.h2.compress.CompressDeflate; @@ -367,7 +368,7 @@ public class MVStore implements AutoCloseable { /** * Callback for maintenance after some unused store versions were dropped */ - private Cleaner cleaner; + private volatile LongConsumer oldestVersionTracker; /** @@ -585,7 +586,7 @@ private void unlockAndCheckPanicCondition() { } } - private void panic(MVStoreException e) { + public void panic(MVStoreException e) { if (isOpen()) { handleException(e); panicException = e; @@ -956,8 +957,6 @@ private void readStoreHeader() { if (test == null || test.version <= newest.version) { break; } - // if shutdown was really clean then chain should be empty - assumeCleanShutdown = false; newest = test; } } @@ -1309,6 +1308,7 @@ private void closeStore(boolean normalShutdown, int allowedCompactionTime) { // This is a subtle difference between !isClosed() and isOpen(). while (!isClosed()) { stopBackgroundThread(normalShutdown); + setOldestVersionTracker(null); storeLock.lock(); try { if (state == STATE_OPEN) { @@ -1316,8 +1316,6 @@ private void closeStore(boolean normalShutdown, int allowedCompactionTime) { try { try { if (normalShutdown && fileStore != null && !fileStore.isReadOnly()) { - // remove all dead LOBs before maps are closed - notifyCleaner(currentVersion); for (MVMap map : maps.values()) { if (map.isClosed()) { deregisterMapRoot(map.getId()); @@ -1368,18 +1366,6 @@ private void closeStore(boolean normalShutdown, int allowedCompactionTime) { } } - private static void shutdownExecutor(ThreadPoolExecutor executor) { - if (executor != null) { - executor.shutdown(); - try { - if (executor.awaitTermination(1000, TimeUnit.MILLISECONDS)) { - return; - } - } catch (InterruptedException ignore) {/**/} - executor.shutdownNow(); - } - } - /** * Get the chunk for the given position. * @@ -1556,7 +1542,7 @@ private static void submitOrRun(ThreadPoolExecutor executor, Runnable action, return; } catch (RejectedExecutionException ex) { assert executor.isShutdown(); - shutdownExecutor(executor); + Utils.shutdownExecutor(executor); } } action.run(); @@ -2040,13 +2026,13 @@ boolean compactMoveChunks(int targetFillRate, long moveSize) { // all task submissions to it are done under storeLock, // it is guaranteed, that upon this dummy task completion // there are no pending / in-progress task here - submitOrRun(serializationExecutor, () -> {}, true); + Utils.flushExecutor(serializationExecutor); serializationLock.lock(); try { // similarly, all task submissions to bufferSaveExecutor // are done under serializationLock, and upon this dummy task completion // it will be no pending / in-progress task here - submitOrRun(bufferSaveExecutor, () -> {}, true); + Utils.flushExecutor(bufferSaveExecutor); saveChunkLock.lock(); try { if (lastChunk != null && reuseSpace && getFillRate() <= targetFillRate) { @@ -2787,41 +2773,22 @@ long getOldestVersionToKeep() { return v; } - private void setOldestVersionToKeep(long oldestVersionToKeep) { + private void setOldestVersionToKeep(long version) { boolean success; do { - long current = this.oldestVersionToKeep.get(); + long current = oldestVersionToKeep.get(); // Oldest version may only advance, never goes back - success = oldestVersionToKeep <= current || - this.oldestVersionToKeep.compareAndSet(current, oldestVersionToKeep); + success = version <= current || + oldestVersionToKeep.compareAndSet(current, version); } while (!success); - notifyAboutOldestVersion(oldestVersionToKeep); - } - - public void setCleaner(Cleaner cleaner) { - this.cleaner = cleaner; - } - private void notifyAboutOldestVersion(long oldestVersionToKeep) { - if (cleaner != null && cleaner.needCleanup() && bufferSaveExecutor != null) { - Runnable blobCleaner = () -> { - notifyCleaner(oldestVersionToKeep); - }; - try { - bufferSaveExecutor.execute(blobCleaner); - } catch (RejectedExecutionException ignore) { - } catch (MVStoreException e) { - panic(e); - } catch (Throwable e) { - panic(DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, "{0}", e.toString(), e)); - } + if (oldestVersionTracker != null) { + oldestVersionTracker.accept(version); } } - public void notifyCleaner(long oldestVersionToKeep) { - if (cleaner != null && cleaner.needCleanup()) { - cleaner.cleanup(oldestVersionToKeep); - } + public void setOldestVersionTracker(LongConsumer callback) { + oldestVersionTracker = callback; } private long lastChunkVersion() { @@ -3413,9 +3380,9 @@ private void stopBackgroundThread(boolean waitForIt) { } } } - shutdownExecutor(serializationExecutor); + Utils.shutdownExecutor(serializationExecutor); serializationExecutor = null; - shutdownExecutor(bufferSaveExecutor); + Utils.shutdownExecutor(bufferSaveExecutor); bufferSaveExecutor = null; break; } @@ -3450,22 +3417,12 @@ public void setAutoCommitDelay(int millis) { fileStore.toString()); if (backgroundWriterThread.compareAndSet(null, t)) { t.start(); - serializationExecutor = createSingleThreadExecutor("H2-serialization"); - bufferSaveExecutor = createSingleThreadExecutor("H2-save"); + serializationExecutor = Utils.createSingleThreadExecutor("H2-serialization"); + bufferSaveExecutor = Utils.createSingleThreadExecutor("H2-save"); } } } - private static ThreadPoolExecutor createSingleThreadExecutor(String threadName) { - return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), - r -> { - Thread thread = new Thread(r, threadName); - thread.setDaemon(true); - return thread; - }); - } - public boolean isBackgroundThread() { return Thread.currentThread() == backgroundWriterThread.get(); } @@ -4135,25 +4092,4 @@ public static Builder fromString(String s) { return new Builder((HashMap) DataUtils.parseMap(s)); } } - - /** - * Callback interface to perform cleanup after some unused store versions were dropped. - * Currently removes LOBs, which are known to be out of scope. - */ - public interface Cleaner { - /** - * Determine if cleanup is needed. - * This is mainly performance optimization to avoid async call to cleanup(). - * - * @return true if cleanup is required at this time, false otherwise - */ - boolean needCleanup(); - - /** - * Actual procedure for cleanup after some unused store versions were dropped - * - * @param oldestVersionToKeep in this MVStore - */ - void cleanup(long oldestVersionToKeep); - } } diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index 948ec39886..02ab3005fa 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -17,6 +17,9 @@ import java.util.Map.Entry; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicLong; import org.h2.api.ErrorCode; import org.h2.engine.Database; @@ -24,6 +27,7 @@ import org.h2.mvstore.DataUtils; import org.h2.mvstore.MVMap; import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; import org.h2.mvstore.StreamStore; import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.tx.TransactionStore; @@ -36,6 +40,7 @@ import org.h2.store.RangeInputStream; import org.h2.util.IOUtils; import org.h2.util.StringUtils; +import org.h2.util.Utils; import org.h2.value.Value; import org.h2.value.ValueBlob; import org.h2.value.ValueClob; @@ -48,13 +53,15 @@ * This class stores LOB objects in the database, in maps. This is the back-end * i.e. the server side of the LOB storage. */ -public final class LobStorageMap implements LobStorageInterface, MVStore.Cleaner +public final class LobStorageMap implements LobStorageInterface { private static final boolean TRACE = false; private final Database database; - final MVStore mvStore; + private final MVStore mvStore; private final AtomicLong nextLobId = new AtomicLong(0); + private final ThreadPoolExecutor cleanupExecutor; + /** * The lob metadata map. It contains the mapping from the lob id @@ -105,7 +112,24 @@ public LobStorageMap(Database database) { Store s = database.getStore(); TransactionStore txStore = s.getTransactionStore(); mvStore = s.getMvStore(); - mvStore.setCleaner(this); + if (mvStore.isVersioningRequired()) { + cleanupExecutor = Utils.createSingleThreadExecutor("H2-lob-cleaner", new SynchronousQueue<>()); + mvStore.setOldestVersionTracker(oldestVersionToKeep -> { + if (needCleanup()) { + try { + cleanupExecutor.execute(() -> { + try { + cleanup(oldestVersionToKeep); + } catch (MVStoreException e) { + mvStore.panic(e); + } + }); + } catch (RejectedExecutionException ignore) {/**/} + } + }); + } else { + cleanupExecutor = null; + } MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { lobMap = openLobMap(txStore); @@ -394,13 +418,23 @@ private void requestLobRemoval(int tableId, long lobId) { pendingLobRemovals.offer(new LobRemovalInfo(mvStore.getCurrentVersion(), lobId, tableId)); } - @Override - public boolean needCleanup() { + private boolean needCleanup() { return !pendingLobRemovals.isEmpty(); } @Override - public void cleanup(long oldestVersionToKeep) { + public void close() { + mvStore.setOldestVersionTracker(null); + Utils.shutdownExecutor(cleanupExecutor); + if (!mvStore.isClosed() && mvStore.isVersioningRequired()) { + // remove all session variables and temporary lobs + removeAllForTable(LobStorageFrontend.TABLE_ID_SESSION_VARIABLE); + // remove all dead LOBs, even deleted in current version, before the store closed + cleanup(mvStore.getCurrentVersion() + 1); + } + } + + private void cleanup(long oldestVersionToKeep) { MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); try { LobRemovalInfo lobRemovalInfo; @@ -417,7 +451,7 @@ public void cleanup(long oldestVersionToKeep) { } } - public void doRemoveLob(int tableId, long lobId) { + private void doRemoveLob(int tableId, long lobId) { if (TRACE) { trace("remove " + tableId + "/" + lobId); } diff --git a/h2/src/main/org/h2/store/LobStorageInterface.java b/h2/src/main/org/h2/store/LobStorageInterface.java index b750c5a83b..2fa4fbd97c 100644 --- a/h2/src/main/org/h2/store/LobStorageInterface.java +++ b/h2/src/main/org/h2/store/LobStorageInterface.java @@ -86,4 +86,9 @@ public interface LobStorageInterface { * @return true if yes */ boolean isReadOnly(); + + /** + * Close LobStorage and release all resources + */ + default void close() {} } diff --git a/h2/src/main/org/h2/util/Utils.java b/h2/src/main/org/h2/util/Utils.java index 4594146fba..e2b965411c 100644 --- a/h2/src/main/org/h2/util/Utils.java +++ b/h2/src/main/org/h2/util/Utils.java @@ -18,6 +18,12 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -745,6 +751,51 @@ public static long nanoTimePlusMillis(long nanoTime, int ms) { return time; } + public static ThreadPoolExecutor createSingleThreadExecutor(String threadName) { + return createSingleThreadExecutor(threadName, new LinkedBlockingQueue<>()); + } + + public static ThreadPoolExecutor createSingleThreadExecutor(String threadName, BlockingQueue workQueue) { + return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, workQueue, + r -> { + Thread thread = new Thread(r, threadName); + thread.setDaemon(true); + return thread; + }); + } + + /** + * Makes sure that all currently submitted tasks are processed before this method returns. + * It is assumed that there will be no new submissions to this executor, once this method has started. + * It is assumed that executor is single-threaded, and flush is done by submitting a dummy task + * and waiting for it's completion. + * @param executor to flush + */ + public static void flushExecutor(ThreadPoolExecutor executor) { + if (executor != null) { + try { + executor.submit(() -> {}).get(); + } catch (InterruptedException ignore) {/**/ + } catch (RejectedExecutionException ex) { + shutdownExecutor(executor); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + } + + public static void shutdownExecutor(ThreadPoolExecutor executor) { + if (executor != null) { + executor.shutdown(); + try { + if (executor.awaitTermination(10, TimeUnit.SECONDS)) { + return; + } + } catch (InterruptedException ignore) {/**/} + executor.shutdownNow(); + } + } + /** * The utility methods will try to use the provided class factories to * convert binary name of class to Class object. Used by H2 OSGi Activator From 746158be34dd03b1a7d25c1be4c45344230276b8 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 3 May 2022 08:57:18 +0800 Subject: [PATCH 55/93] Use common workaround for Path.toRealPath() in FilePathDisk.newDirectoryStream() --- .../org/h2/store/fs/disk/FilePathDisk.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java index 9c30c40cdd..fd9895caca 100644 --- a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java +++ b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java @@ -211,7 +211,7 @@ public void delete() { @Override public List newDirectoryStream() { - try (Stream files = Files.list(Paths.get(name).toRealPath())) { + try (Stream files = Files.list(toRealPath(Paths.get(name)))) { return files.collect(ArrayList::new, (t, u) -> t.add(getPath(u.toString())), ArrayList::addAll); } catch (NoSuchFileException e) { return Collections.emptyList(); @@ -267,19 +267,27 @@ public boolean setReadOnly() { @Override public FilePathDisk toRealPath() { - Path path = Paths.get(name); + return getPath(toRealPath(Paths.get(name)).toString()); + } + + private static Path toRealPath(Path path) { try { - return getPath(path.toRealPath().toString()); + path = path.toRealPath(); } catch (IOException e) { /* * File does not exist or isn't accessible, try to get the real path * of parent directory. + * + * toRealPath() can also throw AccessDeniedException on accessible + * remote directory on Windows if other directories on remote drive + * aren't accessible, but toAbsolutePath() should work. */ - return getPath(toRealPath(path.toAbsolutePath().normalize()).toString()); + path = parentToRealPath(path.toAbsolutePath().normalize()); } + return path; } - private static Path toRealPath(Path path) { + private static Path parentToRealPath(Path path) { Path parent = path.getParent(); if (parent == null) { return path; @@ -287,7 +295,7 @@ private static Path toRealPath(Path path) { try { parent = parent.toRealPath(); } catch (IOException e) { - parent = toRealPath(parent); + parent = parentToRealPath(parent); } return parent.resolve(path.getFileName()); } From 32ab113737006c634258304ea3883b8f30931ed5 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 3 May 2022 09:11:51 +0800 Subject: [PATCH 56/93] Don't call toRealPath() from FileLister.getDatabaseFiles() --- h2/src/main/org/h2/store/FileLister.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/h2/src/main/org/h2/store/FileLister.java b/h2/src/main/org/h2/store/FileLister.java index 2fc6f5a420..fd315d82c4 100644 --- a/h2/src/main/org/h2/store/FileLister.java +++ b/h2/src/main/org/h2/store/FileLister.java @@ -86,10 +86,10 @@ public static String getDir(String dir) { public static ArrayList getDatabaseFiles(String dir, String db, boolean all) { ArrayList files = new ArrayList<>(); - // for Windows, File.getCanonicalPath("...b.") returns just "...b" - String start = db == null ? null : (FileUtils.toRealPath(dir + "/" + db) + "."); - for (String f : FileUtils.newDirectoryStream(dir)) { + String start = db == null ? null : db + '.'; + for (FilePath path : FilePath.get(dir).newDirectoryStream()) { boolean ok = false; + String f = path.toString(); if (f.endsWith(Constants.SUFFIX_MV_FILE)) { ok = true; } else if (all) { @@ -102,7 +102,7 @@ public static ArrayList getDatabaseFiles(String dir, String db, } } if (ok) { - if (db == null || f.startsWith(start)) { + if (db == null || path.getName().startsWith(start)) { files.add(f); } } From 57658d28f9692d0f455ec6787602676ea3701f1c Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 3 May 2022 09:35:31 +0800 Subject: [PATCH 57/93] Update changelog --- h2/src/docsrc/html/changelog.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 2552c84e4f..1c2adfcd6a 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,7 +21,11 @@

          Change Log

          Next Version (unreleased)

            -
          • Nothing yet ... +
          • Issue #3493: org.h2.tools.DeleteDbFiles won't delete files under certain circumstances +
          • +
          • Issue #3486: FilePathDisk.newDirectoryStream() may fail on remote drive on Windows due to AccessDeniedException in Path.toRealPath() +
          • +
          • Issue #3484: LOB issue
          From f98e85905ed9fc124638b5ee6b3875c756254f7b Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 10 May 2022 21:10:48 +0800 Subject: [PATCH 58/93] Remap TEXT to VARCHAR and update documentation of data types --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/res/help.csv | 33 ++++++++----------- h2/src/main/org/h2/value/DataType.java | 7 ++-- .../org/h2/test/scripts/datatypes/clob.sql | 11 ++----- .../org/h2/test/scripts/datatypes/varchar.sql | 11 +++++-- h2/src/tools/org/h2/build/doc/dictionary.txt | 2 +- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 1c2adfcd6a..1883e76156 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 #3444: Conversion 'text' to 'integer' doesn't work anymore +
          • Issue #3493: org.h2.tools.DeleteDbFiles won't delete files under certain circumstances
          • Issue #3486: FilePathDisk.newDirectoryStream() may fail on remote drive on Windows due to AccessDeniedException in Path.toRealPath() diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 381726fb6a..37d35db84c 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -3899,7 +3899,7 @@ ID A Unicode String of fixed length. Length, if any, should be specified in characters, CHARACTERS and OCTETS units have no effect in H2. -The allowed length is from 1 to 1048576 characters. +The allowed length is from 1 to 1,000,000,000 characters. If length is not specified, 1 character is used by default. The whole text is kept in memory when using this data type. @@ -3925,14 +3925,13 @@ CHAR(10) { { CHARACTER | CHAR } VARYING | VARCHAR | { NATIONAL { CHARACTER | CHAR } | NCHAR } VARYING - | @c@ { LONGVARCHAR | VARCHAR2 | NVARCHAR | NVARCHAR2 } | @h2@ { VARCHAR_CASESENSITIVE } } [ ( lengthInt [CHARACTERS|OCTETS] ) ] "," A Unicode String. Use two single quotes ('') to create a quote. -The allowed length is from 1 to 1048576 characters. +The allowed length is from 1 to 1,000,000,000 characters. The length is a size constraint; only the actual data is persisted. Length, if any, should be specified in characters, CHARACTERS and OCTETS units have no effect in H2. @@ -3949,8 +3948,7 @@ VARCHAR(255) "Data Types","CHARACTER LARGE OBJECT Type"," { { CHARACTER | CHAR } LARGE OBJECT | CLOB - | { NATIONAL CHARACTER | NCHAR } LARGE OBJECT | NCLOB - | @c@ { TINYTEXT | TEXT | MEDIUMTEXT | LONGTEXT | NTEXT } } + | { NATIONAL CHARACTER | NCHAR } LARGE OBJECT | NCLOB } [ ( lengthLong [K|M|G|T|P] [CHARACTERS|OCTETS]) ] "," CHARACTER LARGE OBJECT is intended for very large Unicode character string values. @@ -3980,7 +3978,7 @@ CLOB(10K) Same as VARCHAR, but not case sensitive when comparing. Stored in mixed case. -The allowed length is from 1 to 1048576 characters. +The allowed length is from 1 to 1,000,000,000 characters. The length is a size constraint; only the actual data is persisted. Length, if any, should be specified in characters, CHARACTERS and OCTETS units have no effect in H2. @@ -3998,7 +3996,7 @@ BINARY [ ( lengthInt ) ] "," Represents a binary string (byte array) of fixed predefined length. -The allowed length is from 1 to 1048576 bytes. +The allowed length is from 1 to 1,000,000,000 bytes. If length is not specified, 1 byte is used by default. The whole binary string is kept in memory when using this data type. @@ -4020,13 +4018,12 @@ BINARY(1000) " "Data Types","BINARY VARYING Type"," -{ BINARY VARYING | VARBINARY - | @c@ { LONGVARBINARY | RAW | BYTEA } } +{ BINARY VARYING | VARBINARY } [ ( lengthInt ) ] "," Represents a byte array. -The allowed length is from 1 to 1048576 bytes. +The allowed length is from 1 to 1,000,000,000 bytes. The length is a size constraint; only the actual data is persisted. The whole binary string is kept in memory when using this data type. @@ -4041,8 +4038,7 @@ VARBINARY(1000) " "Data Types","BINARY LARGE OBJECT Type"," -{ BINARY LARGE OBJECT | BLOB - | @c@ { TINYBLOB | MEDIUMBLOB | LONGBLOB | IMAGE } } +{ BINARY LARGE OBJECT | BLOB } [ ( lengthLong [K|M|G|T|P]) ] "," BINARY LARGE OBJECT is intended for very large binary values such as files or images. @@ -4059,7 +4055,7 @@ BLOB(10K) " "Data Types","BOOLEAN Type"," -BOOLEAN | @c@ { BIT | BOOL } +BOOLEAN "," Possible values: TRUE, FALSE, and UNKNOWN (NULL). @@ -4087,7 +4083,7 @@ TINYINT " "Data Types","SMALLINT Type"," -SMALLINT | @c@ { INT2 } +SMALLINT "," Possible values: -32768 to 32767. @@ -4103,7 +4099,7 @@ SMALLINT " "Data Types","INTEGER Type"," -INTEGER | INT | @c@ { MEDIUMINT | INT4 | SIGNED } +INTEGER | INT "," Possible values: -2147483648 to 2147483647. @@ -4115,7 +4111,7 @@ INT " "Data Types","BIGINT Type"," -BIGINT | @c@ INT8 +BIGINT "," Possible values: -9223372036854775808 to 9223372036854775807. @@ -4141,7 +4137,7 @@ NUMERIC(20, 2) " "Data Types","REAL Type"," -REAL | FLOAT ( precisionInt ) | @c@ { FLOAT4 } +REAL | FLOAT ( precisionInt ) "," A single precision floating point number. Should not be used to represent currency values, because of rounding problems. @@ -4154,7 +4150,7 @@ REAL " "Data Types","DOUBLE PRECISION Type"," -DOUBLE PRECISION | FLOAT [ ( precisionInt ) ] | @c@ { DOUBLE | FLOAT8 } +DOUBLE PRECISION | FLOAT [ ( precisionInt ) ] "," A double precision floating point number. Should not be used to represent currency values, because of rounding problems. @@ -4252,7 +4248,6 @@ TIME(9) WITH TIME ZONE "Data Types","TIMESTAMP Type"," TIMESTAMP [ ( precisionInt ) ] [ WITHOUT TIME ZONE ] - | @c@ { DATETIME [ ( precisionInt ) ] | SMALLDATETIME } "," The timestamp data type. The proleptic Gregorian calendar is used. If fractional seconds precision is specified it should be from 0 to 9, 6 is default. diff --git a/h2/src/main/org/h2/value/DataType.java b/h2/src/main/org/h2/value/DataType.java index 29ec4fcb10..c2443f3574 100644 --- a/h2/src/main/org/h2/value/DataType.java +++ b/h2/src/main/org/h2/value/DataType.java @@ -123,10 +123,11 @@ public class DataType { "NCHAR VARYING", "NATIONAL CHARACTER VARYING", "NATIONAL CHAR VARYING", "VARCHAR2", "NVARCHAR", "NVARCHAR2", "VARCHAR_CASESENSITIVE", "TID", - "LONGVARCHAR", "LONGNVARCHAR"); + "LONGVARCHAR", "LONGNVARCHAR", + "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "NTEXT"); add(Value.CLOB, Types.CLOB, createLob(true), - "CHARACTER LARGE OBJECT", "CLOB", "CHAR LARGE OBJECT", "TINYTEXT", "TEXT", "MEDIUMTEXT", - "LONGTEXT", "NTEXT", "NCLOB", "NCHAR LARGE OBJECT", "NATIONAL CHARACTER LARGE OBJECT"); + "CHARACTER LARGE OBJECT", "CLOB", "CHAR LARGE OBJECT", + "NCLOB", "NCHAR LARGE OBJECT", "NATIONAL CHARACTER LARGE OBJECT"); add(Value.VARCHAR_IGNORECASE, Types.VARCHAR, createString(false, false), "VARCHAR_IGNORECASE"); add(Value.BINARY, Types.BINARY, createBinary(true), "BINARY"); add(Value.VARBINARY, Types.VARBINARY, createBinary(false), diff --git a/h2/src/test/org/h2/test/scripts/datatypes/clob.sql b/h2/src/test/org/h2/test/scripts/datatypes/clob.sql index 20cb6db086..ffdb7c692f 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/clob.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/clob.sql @@ -3,8 +3,8 @@ -- Initial Developer: H2 Group -- -CREATE TABLE TEST(C1 CLOB, C2 CHARACTER LARGE OBJECT, C3 TINYTEXT, C4 TEXT, C5 MEDIUMTEXT, C6 LONGTEXT, C7 NTEXT, - C8 NCLOB, C9 CHAR LARGE OBJECT, C10 NCHAR LARGE OBJECT, C11 NATIONAL CHARACTER LARGE OBJECT); +CREATE TABLE TEST(C1 CLOB, C2 CHARACTER LARGE OBJECT, C3 NCLOB, + C4 CHAR LARGE OBJECT, C5 NCHAR LARGE OBJECT, C6 NATIONAL CHARACTER LARGE OBJECT); > ok SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS @@ -17,12 +17,7 @@ SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS > C4 CHARACTER LARGE OBJECT > C5 CHARACTER LARGE OBJECT > C6 CHARACTER LARGE OBJECT -> C7 CHARACTER LARGE OBJECT -> C8 CHARACTER LARGE OBJECT -> C9 CHARACTER LARGE OBJECT -> C10 CHARACTER LARGE OBJECT -> C11 CHARACTER LARGE OBJECT -> rows (ordered): 11 +> rows (ordered): 6 DROP TABLE TEST; > ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql b/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql index dd85699a3f..38d43f7a1c 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql @@ -12,7 +12,9 @@ SELECT N'A' 'b' >> Abc CREATE TABLE TEST(C1 VARCHAR, C2 CHARACTER VARYING, C3 VARCHAR2, C4 NVARCHAR, C5 NVARCHAR2, C6 VARCHAR_CASESENSITIVE, - C7 LONGVARCHAR, C8 TID, C9 CHAR VARYING, C10 NCHAR VARYING, C11 NATIONAL CHARACTER VARYING, C12 NATIONAL CHAR VARYING); + C7 LONGVARCHAR, C8 TID, C9 CHAR VARYING, + C10 NCHAR VARYING, C11 NATIONAL CHARACTER VARYING, C12 NATIONAL CHAR VARYING, + C13 TINYTEXT, C14 TEXT, C15 MEDIUMTEXT, C16 LONGTEXT, C17 NTEXT); > ok SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS @@ -31,7 +33,12 @@ SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS > C10 CHARACTER VARYING > C11 CHARACTER VARYING > C12 CHARACTER VARYING -> rows (ordered): 12 +> C13 CHARACTER VARYING +> C14 CHARACTER VARYING +> C15 CHARACTER VARYING +> C16 CHARACTER VARYING +> C17 CHARACTER VARYING +> rows (ordered): 17 DROP TABLE TEST; > ok diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt index 32bf11dd5e..ebc7a03276 100644 --- a/h2/src/tools/org/h2/build/doc/dictionary.txt +++ b/h2/src/tools/org/h2/build/doc/dictionary.txt @@ -849,4 +849,4 @@ entirely skeleton discouraged pearson coefficient squares covariance mytab debug filestore backstop tie breaker lockable lobtx btx waiter accounted aiobe spf resolvers generators abandoned accidental approximately cited competitive configuring drastically happier hasn interactions journal journaling ldt occasional odt officially pragma ration recognising rnrn rough seemed sonatype supplementary subtree ver -wal wbr worse xerial +wal wbr worse xerial won From 6a0f7a973251409f428565c7544dbd272de856f3 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 10 May 2022 21:33:23 +0800 Subject: [PATCH 59/93] Disallow AUTO_SERVER=TRUE && DB_CLOSE_ON_EXIT=FALSE --- h2/src/main/org/h2/engine/Database.java | 3 +++ h2/src/test/org/h2/test/server/TestAutoServer.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 358d1f3119..0f66fdade0 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -272,6 +272,9 @@ public Database(ConnectionInfo ci, String cipher) { } this.allowBuiltinAliasOverride = ci.getProperty("BUILTIN_ALIAS_OVERRIDE", false); boolean closeAtVmShutdown = dbSettings.dbCloseOnExit; + if (autoServerMode && !closeAtVmShutdown) { + throw DbException.getUnsupportedException("AUTO_SERVER=TRUE && DB_CLOSE_ON_EXIT=FALSE"); + } int traceLevelFile = ci.getIntProperty(SetTypes.TRACE_LEVEL_FILE, TraceSystem.DEFAULT_TRACE_LEVEL_FILE); int traceLevelSystemOut = ci.getIntProperty(SetTypes.TRACE_LEVEL_SYSTEM_OUT, TraceSystem.DEFAULT_TRACE_LEVEL_SYSTEM_OUT); diff --git a/h2/src/test/org/h2/test/server/TestAutoServer.java b/h2/src/test/org/h2/test/server/TestAutoServer.java index 72090a0130..9ed556fc8f 100644 --- a/h2/src/test/org/h2/test/server/TestAutoServer.java +++ b/h2/src/test/org/h2/test/server/TestAutoServer.java @@ -49,7 +49,8 @@ private void testUnsupportedCombinations() { "jdbc:h2:" + getTestName() + ";file_lock=no;auto_server=true", "jdbc:h2:" + getTestName() + ";file_lock=serialized;auto_server=true", "jdbc:h2:" + getTestName() + ";access_mode_data=r;auto_server=true", - "jdbc:h2:mem:" + getTestName() + ";auto_server=true" + "jdbc:h2:" + getTestName() + ";AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE", + "jdbc:h2:mem:" + getTestName() + ";AUTO_SERVER=TRUE", }; for (String url : urls) { assertThrows(SQLException.class, () -> getConnection(url)); From 39ddbbedab54f530317de7896ddc1e75ffdb502e Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 10 May 2022 21:43:15 +0800 Subject: [PATCH 60/93] Add parentheses around queries to BNF where they should be required --- h2/src/main/org/h2/res/help.csv | 4 ++-- h2/src/test/org/h2/test/unit/TestBnf.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 37d35db84c..938226c9a8 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -958,7 +958,7 @@ TABLE @h2@ [ IF NOT EXISTS ] [schemaName.]tableName @h2@ [ ENGINE tableEngineName ] @h2@ [ WITH tableEngineParamName [,...] ] @h2@ [ NOT PERSISTENT ] @h2@ [ TRANSACTIONAL ] -[ AS query [ WITH [ NO ] DATA ] ]"," +[ AS ( query ) [ WITH [ NO ] DATA ] ]"," Creates a new table. Cached tables (the default for regular tables) are persistent, @@ -3851,7 +3851,7 @@ CURRENT ROW | ( expression ) | arrayElementReference | fieldReference - | query + | ( query ) | caseExpression | castSpecification | userDefinedFunctionName } diff --git a/h2/src/test/org/h2/test/unit/TestBnf.java b/h2/src/test/org/h2/test/unit/TestBnf.java index 71f9113c64..27dfe00dcd 100644 --- a/h2/src/test/org/h2/test/unit/TestBnf.java +++ b/h2/src/test/org/h2/test/unit/TestBnf.java @@ -138,10 +138,10 @@ private void testProcedures(Connection conn, boolean isMySQLMode) assertTrue(tokens.values().contains("INT")); // Test identifiers are working - tokens = bnf.getNextTokenList("create table \"test\" as s" + "el"); + tokens = bnf.getNextTokenList("create table \"test\" as (s" + "el"); assertTrue(tokens.values().contains("E" + "CT")); - tokens = bnf.getNextTokenList("create table test as s" + "el"); + tokens = bnf.getNextTokenList("create table test as (s" + "el"); assertTrue(tokens.values().contains("E" + "CT")); // Test || with and without spaces From eaf9109eeac6de5c35701b2eea2c726a062738b7 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 10 May 2022 22:24:21 +0800 Subject: [PATCH 61/93] Additional fixes in documentation --- h2/src/main/org/h2/res/help.csv | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 938226c9a8..2a93f5c7c8 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -4251,7 +4251,6 @@ TIMESTAMP [ ( precisionInt ) ] [ WITHOUT TIME ZONE ] "," The timestamp data type. The proleptic Gregorian calendar is used. If fractional seconds precision is specified it should be from 0 to 9, 6 is default. -Fractional seconds precision of SMALLDATETIME is always 0 and cannot be specified. This data type holds the local date and time without time zone information. It cannot distinguish timestamps near transitions from DST to normal time. @@ -4353,8 +4352,8 @@ A type with enumerated values. Mapped to ""java.lang.String"". Duplicate and empty values are not permitted. -The maximum allowed length of value is 1048576 characters. The maximum number of values is 65536. +The maximum allowed length of complete data type definition with all values is 1,000,000,000 characters. "," ENUM('clubs', 'diamonds', 'hearts', 'spades') " From 8027b1d891018a72eb9f23216255ad681ca63720 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 11 May 2022 12:12:53 +0800 Subject: [PATCH 62/93] Fix GEOMETRY, JSON, and JAVA_OBJECT limits in documentation and move check back to ValueBytesBase --- h2/src/main/org/h2/res/help.csv | 7 ++++--- h2/src/main/org/h2/value/ValueBinary.java | 9 +-------- h2/src/main/org/h2/value/ValueBytesBase.java | 7 +++++++ h2/src/main/org/h2/value/ValueJavaObject.java | 7 ------- h2/src/main/org/h2/value/ValueJson.java | 6 ------ h2/src/main/org/h2/value/ValueVarbinary.java | 9 +-------- 6 files changed, 13 insertions(+), 32 deletions(-) diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 2a93f5c7c8..791a92bc82 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -4321,7 +4321,7 @@ INTERVAL DAY TO SECOND @h2@ { JAVA_OBJECT | OBJECT | OTHER } [ ( lengthInt ) ] "," This type allows storing serialized Java objects. Internally, a byte array with serialized form is used. -The allowed length is from 1 (useful only with custom serializer) to 1048576 bytes. +The allowed length is from 1 (useful only with custom serializer) to 1,000,000,000 bytes. The length is a size constraint; only the actual data is persisted. Serialization and deserialization is done on the client side only with two exclusions described below. @@ -4381,7 +4381,8 @@ A constraint with required spatial reference system identifier (SRID) can be set Mapped to ""org.locationtech.jts.geom.Geometry"" if JTS library is in classpath and to ""java.lang.String"" otherwise. May be represented in textual format using the WKT (well-known text) or EWKT (extended well-known text) format. -Values are stored internally in EWKB (extended well-known binary) format, the maximum allowed length is 1048576 bytes. +Values are stored internally in EWKB (extended well-known binary) format, +the maximum allowed length is 1,000,000,000 bytes. Only a subset of EWKB and EWKT features is supported. Supported objects are POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, and GEOMETRYCOLLECTION. Supported dimension systems are 2D (XY), Z (XYZ), M (XYM), and ZM (XYZM). @@ -4404,7 +4405,7 @@ A RFC 8259-compliant JSON text. See also [json](https://h2database.com/html/grammar.html#json) literal grammar. Mapped to ""byte[]"". -The allowed length is from 1 to 1048576 bytes. +The allowed length is from 1 to 1,000,000,000 bytes. The length is a size constraint; only the actual data is persisted. To set a JSON value with ""java.lang.String"" in a PreparedStatement use a ""FORMAT JSON"" data format diff --git a/h2/src/main/org/h2/value/ValueBinary.java b/h2/src/main/org/h2/value/ValueBinary.java index f20a5a71f1..8da0ce07b4 100644 --- a/h2/src/main/org/h2/value/ValueBinary.java +++ b/h2/src/main/org/h2/value/ValueBinary.java @@ -6,10 +6,8 @@ package org.h2.value; import java.nio.charset.StandardCharsets; -import org.h2.engine.Constants; + import org.h2.engine.SysProperties; -import org.h2.message.DbException; -import org.h2.util.StringUtils; import org.h2.util.Utils; /** @@ -24,11 +22,6 @@ public final class ValueBinary extends ValueBytesBase { private ValueBinary(byte[] value) { super(value); - int length = value.length; - if (length > Constants.MAX_STRING_LENGTH) { - throw DbException.getValueTooLongException(getTypeName(getValueType()), - StringUtils.convertBytesToHex(value, 41), length); - } } /** diff --git a/h2/src/main/org/h2/value/ValueBytesBase.java b/h2/src/main/org/h2/value/ValueBytesBase.java index aac8da502b..9b5a527dd2 100644 --- a/h2/src/main/org/h2/value/ValueBytesBase.java +++ b/h2/src/main/org/h2/value/ValueBytesBase.java @@ -8,6 +8,8 @@ import java.util.Arrays; import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.message.DbException; import org.h2.util.Bits; import org.h2.util.StringUtils; import org.h2.util.Utils; @@ -28,6 +30,11 @@ abstract class ValueBytesBase extends Value { int hash; ValueBytesBase(byte[] value) { + int length = value.length; + if (length > Constants.MAX_STRING_LENGTH) { + throw DbException.getValueTooLongException(getTypeName(getValueType()), + StringUtils.convertBytesToHex(value, 41), length); + } this.value = value; } diff --git a/h2/src/main/org/h2/value/ValueJavaObject.java b/h2/src/main/org/h2/value/ValueJavaObject.java index 8629a02f9c..f855fb3120 100644 --- a/h2/src/main/org/h2/value/ValueJavaObject.java +++ b/h2/src/main/org/h2/value/ValueJavaObject.java @@ -6,10 +6,8 @@ package org.h2.value; import org.h2.api.ErrorCode; -import org.h2.engine.Constants; import org.h2.engine.SysProperties; import org.h2.message.DbException; -import org.h2.util.StringUtils; import org.h2.util.Utils; /** @@ -21,11 +19,6 @@ public final class ValueJavaObject extends ValueBytesBase { private ValueJavaObject(byte[] v) { super(v); - int length = value.length; - if (length > Constants.MAX_STRING_LENGTH) { - throw DbException.getValueTooLongException(getTypeName(getValueType()), - StringUtils.convertBytesToHex(value, 41), length); - } } /** diff --git a/h2/src/main/org/h2/value/ValueJson.java b/h2/src/main/org/h2/value/ValueJson.java index aa0011a7ec..ea28869555 100644 --- a/h2/src/main/org/h2/value/ValueJson.java +++ b/h2/src/main/org/h2/value/ValueJson.java @@ -11,7 +11,6 @@ import java.util.Arrays; import org.h2.api.ErrorCode; -import org.h2.engine.Constants; import org.h2.message.DbException; import org.h2.util.StringUtils; import org.h2.util.json.JSONByteArrayTarget; @@ -51,11 +50,6 @@ public final class ValueJson extends ValueBytesBase { private ValueJson(byte[] value) { super(value); - int length = value.length; - if (length > Constants.MAX_STRING_LENGTH) { - throw DbException.getValueTooLongException(getTypeName(getValueType()), - StringUtils.convertBytesToHex(value, 41), length); - } } @Override diff --git a/h2/src/main/org/h2/value/ValueVarbinary.java b/h2/src/main/org/h2/value/ValueVarbinary.java index 12fa6bee16..d6274e0a9f 100644 --- a/h2/src/main/org/h2/value/ValueVarbinary.java +++ b/h2/src/main/org/h2/value/ValueVarbinary.java @@ -6,10 +6,8 @@ package org.h2.value; import java.nio.charset.StandardCharsets; -import org.h2.engine.Constants; + import org.h2.engine.SysProperties; -import org.h2.message.DbException; -import org.h2.util.StringUtils; import org.h2.util.Utils; /** @@ -29,11 +27,6 @@ public final class ValueVarbinary extends ValueBytesBase { private ValueVarbinary(byte[] value) { super(value); - int length = value.length; - if (length > Constants.MAX_STRING_LENGTH) { - throw DbException.getValueTooLongException(getTypeName(getValueType()), - StringUtils.convertBytesToHex(value, 41), length); - } } /** From c109726b9aad1278463afafd45654c472425308b Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Wed, 11 May 2022 22:05:34 +0800 Subject: [PATCH 63/93] Fix TypeInfo.getHigherGeometry() for types with and without SRID --- h2/src/main/org/h2/value/TypeInfo.java | 48 ++++++++++--------- .../h2/test/scripts/datatypes/geometry.sql | 27 +++++++++++ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/h2/src/main/org/h2/value/TypeInfo.java b/h2/src/main/org/h2/value/TypeInfo.java index 4411d970e7..fb1d7b77c9 100644 --- a/h2/src/main/org/h2/value/TypeInfo.java +++ b/h2/src/main/org/h2/value/TypeInfo.java @@ -626,41 +626,43 @@ public static TypeInfo getHigherType(TypeInfo type1, TypeInfo type2) { } private static TypeInfo getHigherGeometry(TypeInfo type1, TypeInfo type2) { + int t; + Integer srid; ExtTypeInfo ext1 = type1.getExtTypeInfo(), ext2 = type2.getExtTypeInfo(); if (ext1 instanceof ExtTypeInfoGeometry) { if (ext2 instanceof ExtTypeInfoGeometry) { ExtTypeInfoGeometry g1 = (ExtTypeInfoGeometry) ext1, g2 = (ExtTypeInfoGeometry) ext2; - Integer srid = g1.getSrid(); - if (!Objects.equals(srid, g2.getSrid())) { + t = g1.getType(); + srid = g1.getSrid(); + int t2 = g2.getType(); + Integer srid2 = g2.getSrid(); + if (Objects.equals(srid, srid2)) { + if (t == t2) { + return type1; + } else if (srid == null) { + return TYPE_GEOMETRY; + } else { + t = 0; + } + } else if (srid == null || srid2 == null) { + if (t == 0 || t != t2) { + return TYPE_GEOMETRY; + } else { + srid = null; + } + } else { throw DbException.get(ErrorCode.TYPES_ARE_NOT_COMPARABLE_2, type1.getTraceSQL(), type2.getTraceSQL()); } - if (g1.getType() == g2.getType()) { - return type1; - } - return srid == null ? TypeInfo.TYPE_GEOMETRY - : TypeInfo.getTypeInfo(Value.GEOMETRY, -1, -1, new ExtTypeInfoGeometry(0, srid)); } else { - return getHigherGeometry(type1, ext1, type2); + return type2.getValueType() == Value.GEOMETRY ? TypeInfo.TYPE_GEOMETRY : type1; } } else if (ext2 instanceof ExtTypeInfoGeometry) { - return getHigherGeometry(type2, ext2, type1); + return type1.getValueType() == Value.GEOMETRY ? TypeInfo.TYPE_GEOMETRY : type2; } else { - return TypeInfo.TYPE_GEOMETRY; - } - } - - private static TypeInfo getHigherGeometry(TypeInfo geometryType, ExtTypeInfo geometryExt, TypeInfo otherType) { - if (otherType.getValueType() != Value.GEOMETRY) { - return geometryType; - } - ExtTypeInfoGeometry g = (ExtTypeInfoGeometry) geometryExt; - if (g.getType() == 0) { - return geometryType; + return TYPE_GEOMETRY; } - Integer srid = g.getSrid(); - return srid == null ? TypeInfo.TYPE_GEOMETRY - : TypeInfo.getTypeInfo(Value.GEOMETRY, -1, -1, new ExtTypeInfoGeometry(0, srid)); + return new TypeInfo(Value.GEOMETRY, -1L, -1, new ExtTypeInfoGeometry(t, srid)); } private static int dimensions(TypeInfo type) { diff --git a/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql b/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql index 14a3522c76..5ef2e589ab 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql @@ -320,3 +320,30 @@ VALUES NULL UNION VALUES CAST('POINT(1 1)' AS GEOMETRY(POINT)); > POINT (1 1) > null > rows: 2 + +VALUES CAST(GEOMETRY 'POINT EMPTY' AS GEOMETRY) +UNION +VALUES CAST(GEOMETRY 'SRID=10;POINT EMPTY' AS GEOMETRY(GEOMETRY, 10)); +> C1 +> ------------------- +> POINT EMPTY +> SRID=10;POINT EMPTY +> rows: 2 + +VALUES CAST(GEOMETRY 'POINT EMPTY' AS GEOMETRY(POINT)) +UNION +VALUES CAST(GEOMETRY 'SRID=10;POINT EMPTY' AS GEOMETRY(POINT, 10)); +> C1 +> ------------------- +> POINT EMPTY +> SRID=10;POINT EMPTY +> rows: 2 + +VALUES CAST(GEOMETRY 'POINT EMPTY' AS GEOMETRY(POINT)) +UNION +VALUES CAST(GEOMETRY 'SRID=10;MULTIPOINT EMPTY' AS GEOMETRY(MULTIPOINT, 10)); +> C1 +> ------------------------ +> POINT EMPTY +> SRID=10;MULTIPOINT EMPTY +> rows: 2 From 210aabfddaadfe561a5f27d29aa719e9d6104738 Mon Sep 17 00:00:00 2001 From: Hans Aikema Date: Thu, 19 May 2022 02:49:50 +0200 Subject: [PATCH 64/93] Only attempt to create tempDir (and parents) when the last level of it is not a symlink (#3511) --- h2/src/main/org/h2/store/fs/disk/FilePathDisk.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java index fd9895caca..c1831f402b 100644 --- a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java +++ b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java @@ -445,7 +445,10 @@ public FilePath createTempFile(String suffix, boolean inTempDir) throws IOExcept Path file = Paths.get(name + '.').toAbsolutePath(); String prefix = file.getFileName().toString(); if (inTempDir) { - Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir", "."))); + final Path tempDir = Paths.get(System.getProperty("java.io.tmpdir", ".")); + if (!Files.isDirectory(tempDir)) { + Files.createDirectories(tempDir); + } file = Files.createTempFile(prefix, suffix); } else { Path dir = file.getParent(); From 409fcd3da21d3b7067a4779eb8517fb8b2fd39d9 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Fri, 20 May 2022 00:09:01 +0800 Subject: [PATCH 65/93] Fix BITNOT(BIT_..._AGG(...) FILTER(...) OVER(...)) optimization --- h2/src/docsrc/html/changelog.html | 6 +++++ .../aggregate/AbstractAggregate.java | 9 ++++++++ .../analysis/DataAnalysisOperation.java | 9 ++++++++ .../h2/expression/function/BitFunction.java | 7 ++++-- .../functions/aggregate/bit_and_agg.sql | 23 +++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 1883e76156..142b3643bd 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -33,6 +33,12 @@

            Next Version (unreleased)

            Next Version 2.1.212 (2022-04-09)

              +
            • Issue #3512: BITNOT(BIT_NAND_AGG(...) OVER ()) produces wrong result +
            • +
            • Issue #3510: PreparedStatement execution with java.io.tmpdir pointing to a directory symlink results in FileAlreadyExistsException +
            • +
            • PR #3504: Fix TypeInfo.getHigherGeometry() for types with and without SRID +
            • PR #3481: Add support for standard interval literals with precision
            • Issue #3471: Possibility of corruption after SHUTDOWN DEFRAG diff --git a/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java b/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java index 09dbf84f8c..ff074402e9 100644 --- a/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java +++ b/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java @@ -60,6 +60,15 @@ public final boolean isAggregate() { return true; } + /** + * Returns the FILTER condition. + * + * @return the FILTER Condition + */ + public Expression getFilterCondition() { + return filterCondition; + } + /** * Sets the FILTER condition. * diff --git a/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java b/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java index 8cb6ebda12..f60a9cdc2f 100644 --- a/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java +++ b/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java @@ -89,6 +89,15 @@ protected DataAnalysisOperation(Select select) { this.select = select; } + /** + * Returns the OVER condition. + * + * @return the OVER condition + */ + public Window getOverCondition() { + return over; + } + /** * Sets the OVER condition. * diff --git a/h2/src/main/org/h2/expression/function/BitFunction.java b/h2/src/main/org/h2/expression/function/BitFunction.java index ac9cd23508..45f7aebb3a 100644 --- a/h2/src/main/org/h2/expression/function/BitFunction.java +++ b/h2/src/main/org/h2/expression/function/BitFunction.java @@ -655,8 +655,11 @@ private Expression optimizeNot(SessionLocal session) { default: return this; } - return new Aggregate(t, new Expression[] { l.getSubexpression(0) }, l.getSelect(), l.isDistinct()) - .optimize(session); + Aggregate aggregate = new Aggregate(t, new Expression[] { l.getSubexpression(0) }, l.getSelect(), + l.isDistinct()); + aggregate.setFilterCondition(l.getFilterCondition()); + aggregate.setOverCondition(l.getOverCondition()); + return aggregate.optimize(session); } return this; } diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_and_agg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_and_agg.sql index 52212634ed..1883a0def0 100644 --- a/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_and_agg.sql +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_and_agg.sql @@ -44,5 +44,28 @@ SELECT BIT_NAND_AGG(V), BIT_NAND_AGG(V) FILTER (WHERE V <= 0xffffffff0fff) FROM EXPLAIN SELECT BITNOT(BIT_AND_AGG(V)), BITNOT(BIT_NAND_AGG(V)) FROM TEST; >> SELECT BIT_NAND_AGG("V"), BIT_AND_AGG("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ +SELECT + V, + BITNOT(BIT_AND_AGG(V) FILTER (WHERE V > 0) OVER (PARTITION BY BITAND(V, 7) ORDER BY V)) G, + BIT_NAND_AGG(V) FILTER (WHERE V > 0) OVER (PARTITION BY BITAND(V, 7) ORDER BY V) C FROM TEST; +> V G C +> --------------- ---------------- ---------------- +> 17592186044415 -17592186044416 -17592186044416 +> 264982302294015 -1099511627776 -1099511627776 +> 280444184559615 -68719476736 -68719476736 +> 281410552201215 -4294967296 -4294967296 +> 281470950178815 -268435456 -268435456 +> 281474725052415 -16777216 -16777216 +> 281474960982015 -1048576 -1048576 +> 281474975727615 -65536 -65536 +> 281474976649215 -4096 -4096 +> 281474976706815 -256 -256 +> 281474976710415 -16 -16 +> 281474976710640 -281474976710641 -281474976710641 +> rows: 12 + +EXPLAIN SELECT BITNOT(BIT_AND_AGG(V) FILTER (WHERE V > 0) OVER (PARTITION BY BITAND(V, 7) ORDER BY V)) FROM TEST; +>> SELECT BIT_NAND_AGG("V") FILTER (WHERE "V" > CAST(0 AS BIGINT)) OVER (PARTITION BY BITAND("V", 7) ORDER BY "V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ + drop table test; > ok From 33151a8f83cbdb935d3b8e5aad2c0775073d65ee Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Fri, 20 May 2022 18:37:31 +0800 Subject: [PATCH 66/93] Allow .NEXTVAL and .CURRVAL in DB2 mode too --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/docsrc/html/features.html | 1 + h2/src/main/org/h2/engine/Mode.java | 1 + h2/src/test/org/h2/test/scripts/other/sequence.sql | 9 +++++++++ 4 files changed, 13 insertions(+) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 142b3643bd..bcbb6f3781 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 #3515: Support for NEXTVAL property in DB2 mode +
              • Issue #3444: Conversion 'text' to 'integer' doesn't work anymore
              • Issue #3493: org.h2.tools.DeleteDbFiles won't delete files under certain circumstances diff --git a/h2/src/docsrc/html/features.html b/h2/src/docsrc/html/features.html index 8dee94ff99..c01cc02bcb 100644 --- a/h2/src/docsrc/html/features.html +++ b/h2/src/docsrc/html/features.html @@ -899,6 +899,7 @@

                DB2 Compatibility Mode

              • Timestamps with dash between date and time are supported.
              • Datetime value functions return the same value within a command.
              • Second and third arguments of TRANSLATE() function are swapped. +
              • SEQUENCE.NEXTVAL and SEQUENCE.CURRVAL are supported
              • LIMIT / OFFSET clauses are supported.
              • MINUS can be used instead of EXCEPT.
              • Unsafe comparison operators between numeric and boolean values are allowed. diff --git a/h2/src/main/org/h2/engine/Mode.java b/h2/src/main/org/h2/engine/Mode.java index 26f875b976..3ae902656c 100644 --- a/h2/src/main/org/h2/engine/Mode.java +++ b/h2/src/main/org/h2/engine/Mode.java @@ -503,6 +503,7 @@ public enum CharPadding { mode.allowDB2TimestampFormat = true; mode.forBitData = true; mode.takeInsertedIdentity = true; + mode.nextvalAndCurrvalPseudoColumns = true; mode.expressionNames = ExpressionNames.NUMBER; mode.viewExpressionNames = ViewExpressionNames.EXCEPTION; mode.limit = true; diff --git a/h2/src/test/org/h2/test/scripts/other/sequence.sql b/h2/src/test/org/h2/test/scripts/other/sequence.sql index 16c2e25f9e..211b18f504 100644 --- a/h2/src/test/org/h2/test/scripts/other/sequence.sql +++ b/h2/src/test/org/h2/test/scripts/other/sequence.sql @@ -144,6 +144,15 @@ SELECT SEQ.NEXTVAL; SELECT SEQ.CURRVAL; > exception COLUMN_NOT_FOUND_1 +SET MODE DB2; +> ok + +SELECT SEQ.NEXTVAL; +>> 1 + +SELECT SEQ.CURRVAL; +>> 1 + DROP SEQUENCE SEQ; > ok From 10a6a1acb5c7b222fca246469c168fc0efc999c6 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 21 May 2022 22:33:25 +0800 Subject: [PATCH 67/93] Pass encryption key to database compaction tool --- h2/src/main/org/h2/engine/Database.java | 2 +- h2/src/main/org/h2/mvstore/MVStoreTool.java | 51 ++++++++++++++++++--- h2/src/main/org/h2/mvstore/db/Store.java | 6 ++- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 0f66fdade0..1e43011960 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -1261,7 +1261,7 @@ private synchronized void closeOpenFilesAndUnlock() { compactMode == CommandInterface.SHUTDOWN_COMPACT || compactMode == CommandInterface.SHUTDOWN_DEFRAG || dbSettings.defragAlways ? -1 : dbSettings.maxCompactTime; - store.close(allowedCompactionTime); + store.close(allowedCompactionTime, fileEncryptionKey); } } if (persistent) { diff --git a/h2/src/main/org/h2/mvstore/MVStoreTool.java b/h2/src/main/org/h2/mvstore/MVStoreTool.java index feefd0b93d..93772fb02d 100644 --- a/h2/src/main/org/h2/mvstore/MVStoreTool.java +++ b/h2/src/main/org/h2/mvstore/MVStoreTool.java @@ -21,6 +21,7 @@ import org.h2.compress.CompressLZF; import org.h2.compress.Compressor; import org.h2.engine.Constants; +import org.h2.mvstore.MVStore.Builder; import org.h2.mvstore.tx.TransactionStore; import org.h2.mvstore.type.BasicDataType; import org.h2.mvstore.type.StringDataType; @@ -436,9 +437,25 @@ private static int getPercent(long value, long max) { * @param compress whether to compress the data */ public static void compact(String fileName, boolean compress) { + compact(fileName, compress, null); + } + + /** + * Compress the store by creating a new file and copying the live pages + * there. Temporarily, a file with the suffix ".tempFile" is created. This + * file is then renamed, replacing the original file, if possible. If not, + * the new file is renamed to ".newFile", then the old file is removed, and + * the new file is renamed. This might be interrupted, so it's better to + * compactCleanUp before opening a store, in case this method was used. + * + * @param fileName the file name + * @param compress whether to compress the data + * @param encryptionKey the encryption key, or {@code null} + */ + public static void compact(String fileName, boolean compress, char[] encryptionKey) { String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; FileUtils.delete(tempName); - compact(fileName, tempName, compress); + compact(fileName, tempName, compress, encryptionKey); try { FileUtils.moveAtomicReplace(tempName, fileName); } catch (MVStoreException e) { @@ -481,20 +498,40 @@ public static void compactCleanUp(String fileName) { * @param compress whether to compress the data */ public static void compact(String sourceFileName, String targetFileName, boolean compress) { - try (MVStore source = new MVStore.Builder(). - fileName(sourceFileName).readOnly().open()) { + compact(sourceFileName, targetFileName, compress, null); + } + + /** + * Copy all live pages from the source store to the target store. + * + * @param sourceFileName the name of the source store + * @param targetFileName the name of the target store + * @param compress whether to compress the data + * @param encryptionKey the encryption key, or {@code null} + */ + public static void compact(String sourceFileName, String targetFileName, boolean compress, + char[] encryptionKey) { + Builder sourceBuilder = new MVStore.Builder().fileName(sourceFileName).readOnly(); + if (encryptionKey != null) { + // Key is erased, so create a copy + char[] key = encryptionKey.clone(); + sourceBuilder.encryptionKey(key); + } + try (MVStore source = sourceBuilder.open()) { // Bugfix - Add double "try-finally" statements to close source and target stores for //releasing lock and file resources in these stores even if OOM occurs. // Fix issues such as "Cannot delete file "/h2/data/test.mv.db.tempFile" [90025-197]" //when client connects to this server and reopens this store database in this process. // @since 2018-09-13 little-pan FileUtils.delete(targetFileName); - MVStore.Builder b = new MVStore.Builder(). - fileName(targetFileName); + MVStore.Builder targetBuilder = new MVStore.Builder().fileName(targetFileName); if (compress) { - b.compress(); + targetBuilder.compress(); + } + if (encryptionKey != null) { + targetBuilder.encryptionKey(encryptionKey); } - try (MVStore target = b.open()) { + try (MVStore target = targetBuilder.open()) { compact(source, target); } } diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index 4f051b2e0d..0f56205d44 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -356,8 +356,9 @@ public void compactFile(int maxCompactTime) { * @param allowedCompactionTime time (in milliseconds) alloted for file * compaction activity, 0 means no compaction, * -1 means unlimited time (full compaction) + * @param fileEncryptionKey the file encryption key, or {@code null} */ - public void close(int allowedCompactionTime) { + public void close(int allowedCompactionTime, byte[] fileEncryptionKey) { try { FileStore fileStore = mvStore.getFileStore(); if (!mvStore.isClosed() && fileStore != null) { @@ -377,7 +378,8 @@ public void close(int allowedCompactionTime) { if (compactFully && FileUtils.exists(fileName)) { // the file could have been deleted concurrently, // so only compact if the file still exists - MVStoreTool.compact(fileName, true); + MVStoreTool.compact(fileName, true, + fileEncryptionKey == null ? null : decodePassword(fileEncryptionKey)); } } } catch (MVStoreException e) { From 32a1aebfdca212955744d1b4c98d103dfb6a05b6 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 23 May 2022 21:30:14 -0400 Subject: [PATCH 68/93] fix compaction of encrypted database, eliminate holding password in memory --- h2/src/main/org/h2/engine/Database.java | 10 +-- h2/src/main/org/h2/mvstore/FileStore.java | 19 +++++- h2/src/main/org/h2/mvstore/MVStore.java | 23 ++++--- h2/src/main/org/h2/mvstore/MVStoreTool.java | 66 ++++++------------- .../main/org/h2/mvstore/db/MVTempResult.java | 9 ++- h2/src/main/org/h2/mvstore/db/Store.java | 33 ++++++++-- .../org/h2/store/fs/encrypt/FileEncrypt.java | 66 ++++++++++++++----- h2/src/test/org/h2/test/store/TestDefrag.java | 13 ++++ 8 files changed, 145 insertions(+), 94 deletions(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 1e43011960..3edb33450b 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -131,7 +131,6 @@ public final class Database implements DataHandler, CastDataProvider { private final String databaseURL; private final String cipher; private final byte[] filePasswordHash; - private final byte[] fileEncryptionKey; private final ConcurrentHashMap usersAndRoles = new ConcurrentHashMap<>(); private final ConcurrentHashMap settings = new ConcurrentHashMap<>(); @@ -227,7 +226,6 @@ public Database(ConnectionInfo ci, String cipher) { this.compareMode = CompareMode.getInstance(null, 0); this.persistent = ci.isPersistent(); this.filePasswordHash = ci.getFilePasswordHash(); - this.fileEncryptionKey = ci.getFileEncryptionKey(); this.databaseName = databaseName; this.databaseShortName = parseDatabaseShortName(); this.maxLengthInplaceLob = Constants.DEFAULT_MAX_LENGTH_INPLACE_LOB; @@ -324,7 +322,7 @@ public Database(ConnectionInfo ci, String cipher) { } starting = true; if (dbSettings.mvStore) { - store = new Store(this); + store = new Store(this, ci.getFileEncryptionKey()); } else { throw new UnsupportedOperationException(); } @@ -1261,7 +1259,7 @@ private synchronized void closeOpenFilesAndUnlock() { compactMode == CommandInterface.SHUTDOWN_COMPACT || compactMode == CommandInterface.SHUTDOWN_DEFRAG || dbSettings.defragAlways ? -1 : dbSettings.maxCompactTime; - store.close(allowedCompactionTime, fileEncryptionKey); + store.close(allowedCompactionTime); } } if (persistent) { @@ -2348,10 +2346,6 @@ public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, i throw DbException.getInternalError(); } - public byte[] getFileEncryptionKey() { - return fileEncryptionKey; - } - public int getPageSize() { return pageSize; } diff --git a/h2/src/main/org/h2/mvstore/FileStore.java b/h2/src/main/org/h2/mvstore/FileStore.java index dc1142fcac..291394dfb4 100644 --- a/h2/src/main/org/h2/mvstore/FileStore.java +++ b/h2/src/main/org/h2/mvstore/FileStore.java @@ -11,6 +11,7 @@ import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; import org.h2.mvstore.cache.FilePathCache; import org.h2.store.fs.FilePath; import org.h2.store.fs.encrypt.FileEncrypt; @@ -124,6 +125,19 @@ public void writeFully(long pos, ByteBuffer src) { * used */ public void open(String fileName, boolean readOnly, char[] encryptionKey) { + open(fileName, readOnly, encryptionKey == null ? null : + fileChannel -> new FileEncrypt(fileName, FilePathEncrypt.getPasswordBytes(encryptionKey), fileChannel)); + } + + public FileStore open(String fileName, boolean readOnly) { + + FileStore result = new FileStore(); + result.open(fileName, readOnly, encryptedFile == null ? null : + fileChannel -> new FileEncrypt(fileName, (FileEncrypt)file, fileChannel)); + return result; + } + + private void open(String fileName, boolean readOnly, Function encryptionTransformer) { if (file != null) { return; } @@ -142,10 +156,9 @@ public void open(String fileName, boolean readOnly, char[] encryptionKey) { this.readOnly = readOnly; try { file = f.open(readOnly ? "r" : "rw"); - if (encryptionKey != null) { - byte[] key = FilePathEncrypt.getPasswordBytes(encryptionKey); + if (encryptionTransformer != null) { encryptedFile = file; - file = new FileEncrypt(fileName, key, file); + file = encryptionTransformer.apply(file); } try { if (readOnly) { diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java index fc2776471b..b72b118d05 100644 --- a/h2/src/main/org/h2/mvstore/MVStore.java +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -29,7 +29,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; @@ -226,7 +225,7 @@ public class MVStore implements AutoCloseable { private final FileStore fileStore; - private final boolean fileStoreIsProvided; + private final boolean fileStoreShallBeClosed; private final int pageSplitSize; @@ -384,16 +383,19 @@ public class MVStore implements AutoCloseable { compressionLevel = DataUtils.getConfigParam(config, "compress", 0); String fileName = (String) config.get("fileName"); FileStore fileStore = (FileStore) config.get("fileStore"); + boolean fileStoreShallBeOpen = false; if (fileStore == null) { - fileStoreIsProvided = false; if (fileName != null) { fileStore = new FileStore(); + fileStoreShallBeOpen = true; } + fileStoreShallBeClosed = true; } else { if (fileName != null) { throw new IllegalArgumentException("fileName && fileStore"); } - fileStoreIsProvided = true; + Boolean fileStoreIsAdopted = (Boolean) config.get("fileStoreIsAdopted"); + fileStoreShallBeClosed = fileStoreIsAdopted != null && fileStoreIsAdopted; } this.fileStore = fileStore; @@ -438,14 +440,14 @@ public class MVStore implements AutoCloseable { kb = DataUtils.getConfigParam(config, "autoCommitBufferSize", kb); autoCommitMemory = kb * 1024; autoCompactFillRate = DataUtils.getConfigParam(config, "autoCompactFillRate", 90); - char[] encryptionKey = (char[]) config.get("encryptionKey"); + char[] encryptionKey = (char[]) config.remove("encryptionKey"); // there is no need to lock store here, since it is not opened (or even created) yet, // just to make some assertions happy, when they ensure single-threaded access storeLock.lock(); try { saveChunkLock.lock(); try { - if (!fileStoreIsProvided) { + if (fileStoreShallBeOpen) { boolean readOnly = config.containsKey("readOnly"); this.fileStore.open(fileName, readOnly, encryptionKey); } @@ -1352,7 +1354,7 @@ private void closeStore(boolean normalShutdown, int allowedCompactionTime) { chunks.clear(); maps.clear(); } finally { - if (fileStore != null && !fileStoreIsProvided) { + if (fileStore != null && fileStoreShallBeClosed) { fileStore.close(); } } @@ -3819,7 +3821,7 @@ boolean isPinned() { * @return removed page info that contains chunk id, page number, page length and pinned flag */ private static long createRemovedPageInfo(long pagePos, boolean isPinned, int pageNo) { - long result = (pagePos & ~((0xFFFFFFFFL << 6) | 1)) | ((pageNo << 6) & 0xFFFFFFFFL); + long result = (pagePos & ~((0xFFFFFFFFL << 6) | 1)) | (((long)pageNo << 6) & 0xFFFFFFFFL); if (isPinned) { result |= 1; } @@ -4066,6 +4068,11 @@ public Builder fileStore(FileStore store) { return set("fileStore", store); } + public Builder adoptFileStore(FileStore store) { + set("fileStoreIsAdopted", true); + return set("fileStore", store); + } + /** * Open the store. * diff --git a/h2/src/main/org/h2/mvstore/MVStoreTool.java b/h2/src/main/org/h2/mvstore/MVStoreTool.java index 93772fb02d..d299099948 100644 --- a/h2/src/main/org/h2/mvstore/MVStoreTool.java +++ b/h2/src/main/org/h2/mvstore/MVStoreTool.java @@ -21,7 +21,6 @@ import org.h2.compress.CompressLZF; import org.h2.compress.Compressor; import org.h2.engine.Constants; -import org.h2.mvstore.MVStore.Builder; import org.h2.mvstore.tx.TransactionStore; import org.h2.mvstore.type.BasicDataType; import org.h2.mvstore.type.StringDataType; @@ -437,33 +436,28 @@ private static int getPercent(long value, long max) { * @param compress whether to compress the data */ public static void compact(String fileName, boolean compress) { - compact(fileName, compress, null); + String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; + FileUtils.delete(tempName); + compact(fileName, tempName, compress); + moveAtomicReplace(tempName, fileName); } /** - * Compress the store by creating a new file and copying the live pages - * there. Temporarily, a file with the suffix ".tempFile" is created. This - * file is then renamed, replacing the original file, if possible. If not, - * the new file is renamed to ".newFile", then the old file is removed, and - * the new file is renamed. This might be interrupted, so it's better to - * compactCleanUp before opening a store, in case this method was used. + * Rename a file(s) of the named store, and try to atomically replace an + * existing file(s) of another store. * - * @param fileName the file name - * @param compress whether to compress the data - * @param encryptionKey the encryption key, or {@code null} + * @param sourceName the old fully qualified file name of the store + * @param destinationName the new fully qualified file name of the store */ - public static void compact(String fileName, boolean compress, char[] encryptionKey) { - String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; - FileUtils.delete(tempName); - compact(fileName, tempName, compress, encryptionKey); + public static void moveAtomicReplace(String sourceName, String destinationName) { try { - FileUtils.moveAtomicReplace(tempName, fileName); + FileUtils.moveAtomicReplace(sourceName, destinationName); } catch (MVStoreException e) { - String newName = fileName + Constants.SUFFIX_MV_STORE_NEW_FILE; + String newName = destinationName + Constants.SUFFIX_MV_STORE_NEW_FILE; FileUtils.delete(newName); - FileUtils.move(tempName, newName); - FileUtils.delete(fileName); - FileUtils.move(newName, fileName); + FileUtils.move(sourceName, newName); + FileUtils.delete(destinationName); + FileUtils.move(newName, destinationName); } } @@ -498,40 +492,20 @@ public static void compactCleanUp(String fileName) { * @param compress whether to compress the data */ public static void compact(String sourceFileName, String targetFileName, boolean compress) { - compact(sourceFileName, targetFileName, compress, null); - } - - /** - * Copy all live pages from the source store to the target store. - * - * @param sourceFileName the name of the source store - * @param targetFileName the name of the target store - * @param compress whether to compress the data - * @param encryptionKey the encryption key, or {@code null} - */ - public static void compact(String sourceFileName, String targetFileName, boolean compress, - char[] encryptionKey) { - Builder sourceBuilder = new MVStore.Builder().fileName(sourceFileName).readOnly(); - if (encryptionKey != null) { - // Key is erased, so create a copy - char[] key = encryptionKey.clone(); - sourceBuilder.encryptionKey(key); - } - try (MVStore source = sourceBuilder.open()) { + try (MVStore source = new MVStore.Builder(). + fileName(sourceFileName).readOnly().open()) { // Bugfix - Add double "try-finally" statements to close source and target stores for //releasing lock and file resources in these stores even if OOM occurs. // Fix issues such as "Cannot delete file "/h2/data/test.mv.db.tempFile" [90025-197]" //when client connects to this server and reopens this store database in this process. // @since 2018-09-13 little-pan FileUtils.delete(targetFileName); - MVStore.Builder targetBuilder = new MVStore.Builder().fileName(targetFileName); + MVStore.Builder b = new MVStore.Builder(). + fileName(targetFileName); if (compress) { - targetBuilder.compress(); - } - if (encryptionKey != null) { - targetBuilder.encryptionKey(encryptionKey); + b.compress(); } - try (MVStore target = targetBuilder.open()) { + try (MVStore target = b.open()) { compact(source, target); } } diff --git a/h2/src/main/org/h2/mvstore/db/MVTempResult.java b/h2/src/main/org/h2/mvstore/db/MVTempResult.java index 97779cba55..ff25d9de9c 100644 --- a/h2/src/main/org/h2/mvstore/db/MVTempResult.java +++ b/h2/src/main/org/h2/mvstore/db/MVTempResult.java @@ -13,6 +13,7 @@ import org.h2.engine.Database; import org.h2.expression.Expression; import org.h2.message.DbException; +import org.h2.mvstore.FileStore; import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore.Builder; import org.h2.result.ResultExternal; @@ -176,11 +177,9 @@ public static ResultExternal of(Database database, Expression[] expressions, boo this.database = database; try { String fileName = FileUtils.createTempFile("h2tmp", Constants.SUFFIX_TEMP_FILE, true); - Builder builder = new MVStore.Builder().fileName(fileName).cacheSize(0).autoCommitDisabled(); - byte[] key = database.getFileEncryptionKey(); - if (key != null) { - builder.encryptionKey(Store.decodePassword(key)); - } + + FileStore fileStore = database.getStore().getMvStore().getFileStore().open(fileName, false); + Builder builder = new MVStore.Builder().adoptFileStore(fileStore).cacheSize(0).autoCommitDisabled(); store = builder.open(); this.expressions = expressions; this.visibleColumnCount = visibleColumnCount; diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index 0f56205d44..2ef675185a 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -86,8 +86,7 @@ static char[] decodePassword(byte[] key) { * * @param db the database */ - public Store(Database db) { - byte[] key = db.getFileEncryptionKey(); + public Store(Database db, byte[] key) { String dbPath = db.getDatabasePath(); MVStore.Builder builder = new MVStore.Builder(); boolean encrypted = false; @@ -356,9 +355,8 @@ public void compactFile(int maxCompactTime) { * @param allowedCompactionTime time (in milliseconds) alloted for file * compaction activity, 0 means no compaction, * -1 means unlimited time (full compaction) - * @param fileEncryptionKey the file encryption key, or {@code null} */ - public void close(int allowedCompactionTime, byte[] fileEncryptionKey) { + public void close(int allowedCompactionTime) { try { FileStore fileStore = mvStore.getFileStore(); if (!mvStore.isClosed() && fileStore != null) { @@ -372,14 +370,21 @@ public void close(int allowedCompactionTime, byte[] fileEncryptionKey) { allowedCompactionTime = 0; } + String fileName = null; + FileStore targetFileStore = null; + if (compactFully) { + fileName = fileStore.getFileName(); + String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; + FileUtils.delete(tempName); + targetFileStore = fileStore.open(tempName, false); + } + mvStore.close(allowedCompactionTime); - String fileName = fileStore.getFileName(); if (compactFully && FileUtils.exists(fileName)) { // the file could have been deleted concurrently, // so only compact if the file still exists - MVStoreTool.compact(fileName, true, - fileEncryptionKey == null ? null : decodePassword(fileEncryptionKey)); + compact(fileName, targetFileStore); } } } catch (MVStoreException e) { @@ -394,6 +399,20 @@ public void close(int allowedCompactionTime, byte[] fileEncryptionKey) { } } + + private static void compact(String sourceFilename, FileStore targetFileStore) { + MVStore.Builder targetBuilder = new MVStore.Builder().compress().adoptFileStore(targetFileStore); + try (MVStore targetMVStore = targetBuilder.open()) { + FileStore sourceFileStore = targetFileStore.open(sourceFilename, true); + MVStore.Builder sourceBuilder = new MVStore.Builder(); + sourceBuilder.readOnly().adoptFileStore(sourceFileStore); + try (MVStore sourceMVStore = sourceBuilder.open()) { + MVStoreTool.compact(sourceMVStore, targetMVStore); + } + } + MVStoreTool.moveAtomicReplace(targetFileStore.getFileName(), sourceFilename); + } + /** * Start collecting statistics. */ diff --git a/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java b/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java index 38bc227b04..b89efa9029 100644 --- a/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java +++ b/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java @@ -12,6 +12,7 @@ import java.nio.channels.FileLock; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import org.h2.mvstore.DataUtils; import org.h2.security.AES; import org.h2.security.SHA256; import org.h2.store.fs.FileBaseDefault; @@ -65,6 +66,8 @@ public class FileEncrypt extends FileBaseDefault { private byte[] encryptionKey; + private FileEncrypt source; + public FileEncrypt(String name, byte[] encryptionKey, FileChannel base) { // don't do any read or write operations here, because they could // fail if the file is locked, and we want to give the caller a @@ -74,6 +77,21 @@ public FileEncrypt(String name, byte[] encryptionKey, FileChannel base) { this.encryptionKey = encryptionKey; } + public FileEncrypt(String name, FileEncrypt source, FileChannel base) { + // don't do any read or write operations here, because they could + // fail if the file is locked, and we want to give the caller a + // chance to lock the file first + this.name = name; + this.base = base; + this.source = source; + try { + source.init(); + } catch (IOException e) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, + "Can not open {0} using encryption of {1}", name, source.name); + } + } + private XTS init() throws IOException { // Keep this method small to allow inlining XTS xts = this.xts; @@ -88,26 +106,40 @@ private synchronized XTS createXTS() throws IOException { if (xts != null) { return xts; } - this.size = base.size() - HEADER_LENGTH; - boolean newFile = size < 0; - byte[] salt; - if (newFile) { - byte[] header = Arrays.copyOf(HEADER, BLOCK_SIZE); - salt = MathUtils.secureRandomBytes(SALT_LENGTH); - System.arraycopy(salt, 0, header, SALT_POS, salt.length); - writeFully(base, 0, ByteBuffer.wrap(header)); - size = 0; + assert size == 0; + long sz = base.size() - HEADER_LENGTH; + boolean existingFile = sz >= 0; + if (encryptionKey != null) { + byte[] salt; + if (existingFile) { + salt = new byte[SALT_LENGTH]; + readFully(base, SALT_POS, ByteBuffer.wrap(salt)); + } else { + byte[] header = Arrays.copyOf(HEADER, BLOCK_SIZE); + salt = MathUtils.secureRandomBytes(SALT_LENGTH); + System.arraycopy(salt, 0, header, SALT_POS, salt.length); + writeFully(base, 0, ByteBuffer.wrap(header)); + } + AES cipher = new AES(); + cipher.setKey(SHA256.getPBKDF2(encryptionKey, salt, HASH_ITERATIONS, 16)); + encryptionKey = null; + xts = new XTS(cipher); } else { - salt = new byte[SALT_LENGTH]; - readFully(base, SALT_POS, ByteBuffer.wrap(salt)); - if ((size & BLOCK_SIZE_MASK) != 0) { - size -= BLOCK_SIZE; + if (!existingFile) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BLOCK_SIZE); + readFully(source.base, 0, byteBuffer); + byteBuffer.flip(); + writeFully(base, 0, byteBuffer); + } + xts = source.xts; + } + if (existingFile) { + if ((sz & BLOCK_SIZE_MASK) != 0) { + sz -= BLOCK_SIZE; } + size = sz; } - AES cipher = new AES(); - cipher.setKey(SHA256.getPBKDF2(encryptionKey, salt, HASH_ITERATIONS, 16)); - encryptionKey = null; - return this.xts = new XTS(cipher); + return this.xts = xts; } @Override diff --git a/h2/src/test/org/h2/test/store/TestDefrag.java b/h2/src/test/org/h2/test/store/TestDefrag.java index b78bab536d..549f0791e2 100644 --- a/h2/src/test/org/h2/test/store/TestDefrag.java +++ b/h2/src/test/org/h2/test/store/TestDefrag.java @@ -39,6 +39,19 @@ public boolean isEnabled() { @Override public void test() throws Exception { + String cipher = config.cipher; + config.traceTest = true; + try { + config.cipher = null; + testIt(); + config.cipher = "AES"; + testIt(); + } finally { + config.cipher = cipher; + } + } + + public void testIt() throws Exception { String dbName = getTestName(); deleteDb(dbName); File dbFile = new File(getBaseDir(), dbName + SUFFIX_MV_FILE); From fac1c491d77a3ddd1bdb396f87966ea7aaeecab9 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Tue, 24 May 2022 19:58:43 -0400 Subject: [PATCH 69/93] clear reference to origunating FileStore after initialization --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java | 1 + 2 files changed, 3 insertions(+) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index bcbb6f3781..767da8b107 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 #3307: Fix SHUTDOWN DEFRAG for encrypted databases +
                • Issue #3515: Support for NEXTVAL property in DB2 mode
                • Issue #3444: Conversion 'text' to 'integer' doesn't work anymore diff --git a/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java b/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java index b89efa9029..1309450f38 100644 --- a/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java +++ b/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java @@ -132,6 +132,7 @@ private synchronized XTS createXTS() throws IOException { writeFully(base, 0, byteBuffer); } xts = source.xts; + source = null; } if (existingFile) { if ((sz & BLOCK_SIZE_MASK) != 0) { From b15772180df2391704471d8f6dce0a77369d5445 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Tue, 24 May 2022 21:56:55 -0400 Subject: [PATCH 70/93] Update h2/src/main/org/h2/mvstore/db/MVTempResult.java Co-authored-by: sonatype-lift[bot] <37194012+sonatype-lift[bot]@users.noreply.github.com> --- h2/src/main/org/h2/mvstore/db/MVTempResult.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/mvstore/db/MVTempResult.java b/h2/src/main/org/h2/mvstore/db/MVTempResult.java index ff25d9de9c..5618d7e79b 100644 --- a/h2/src/main/org/h2/mvstore/db/MVTempResult.java +++ b/h2/src/main/org/h2/mvstore/db/MVTempResult.java @@ -179,7 +179,7 @@ public static ResultExternal of(Database database, Expression[] expressions, boo String fileName = FileUtils.createTempFile("h2tmp", Constants.SUFFIX_TEMP_FILE, true); FileStore fileStore = database.getStore().getMvStore().getFileStore().open(fileName, false); - Builder builder = new MVStore.Builder().adoptFileStore(fileStore).cacheSize(0).autoCommitDisabled(); + MVStore.Builder builder = new MVStore.Builder().adoptFileStore(fileStore).cacheSize(0).autoCommitDisabled(); store = builder.open(); this.expressions = expressions; this.visibleColumnCount = visibleColumnCount; From 3f84978a162374923d8f9121fcbeae35352114b0 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 4 Jun 2022 11:30:52 +0800 Subject: [PATCH 71/93] Prevent generation of aliases for having and qualify expressions --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/command/query/Select.java | 24 +++++++++++++------ .../scripts/compatibility/compatibility.sql | 15 ++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 767da8b107..a20b64af31 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 #3528: Weird syntax error with HAVING clause in Oracle Mode +
                  • Issue #3307: Fix SHUTDOWN DEFRAG for encrypted databases
                  • Issue #3515: Support for NEXTVAL property in DB2 mode diff --git a/h2/src/main/org/h2/command/query/Select.java b/h2/src/main/org/h2/command/query/Select.java index c1795ec986..afb6aeec2b 100644 --- a/h2/src/main/org/h2/command/query/Select.java +++ b/h2/src/main/org/h2/command/query/Select.java @@ -1273,13 +1273,23 @@ public void preparePlan() { private void optimizeExpressionsAndPreserveAliases() { for (int i = 0; i < expressions.size(); i++) { - Expression e = expressions.get(i); - String alias = e.getAlias(session, i); - e = e.optimize(session); - if (!e.getAlias(session, i).equals(alias)) { - e = new Alias(e, alias, true); + Expression original = expressions.get(i); + /* + * TODO cannot evaluate optimized now, because some optimize() + * methods violate their contract and modify the original + * expression. + */ + Expression optimized; + if (i < visibleColumnCount) { + String alias = original.getAlias(session, i); + optimized = original.optimize(session); + if (!optimized.getAlias(session, i).equals(alias)) { + optimized = new Alias(optimized, alias, true); + } + } else { + optimized = original.optimize(session); } - expressions.set(i, e); + expressions.set(i, optimized); } } @@ -1492,7 +1502,7 @@ private static void getFilterSQL(StringBuilder builder, String sql, Expression[] } private static void getFilterSQL(StringBuilder builder, String sql, Expression condition, int sqlFlags) { - condition.getUnenclosedSQL(builder.append(sql), sqlFlags); + condition.getNonAliasExpression().getUnenclosedSQL(builder.append(sql), sqlFlags); } private static boolean containsAggregate(Expression expression) { diff --git a/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql b/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql index 97db29e968..995c1ab211 100644 --- a/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql +++ b/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql @@ -496,6 +496,21 @@ SET MODE Regular; DROP TABLE TEST; > ok +CREATE TABLE TEST(A INT, B INT) AS (VALUES (1, 2), (1, 3), (2, 4)); +> ok + +SET MODE Oracle; +> ok + +EXPLAIN SELECT * FROM (SELECT A, SUM(B) FROM TEST HAVING COUNT(B) > 1 OR A = 1 OR A = 2) WHERE A <> 3; +>> SELECT "_0"."A", "_0"."SUM(B)" FROM ( SELECT "A", SUM("B") FROM "PUBLIC"."TEST" HAVING ("A" IN(1, 2)) OR (COUNT("B") > 1) ) "_0" /* SELECT A, SUM(B) FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ HAVING (A IN(1, 2)) OR (COUNT(B) > 1) */ WHERE "A" <> 3 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + --- sequence with manual value ------------------ SET MODE MySQL; From 51ffcb43154e54dc0c6559e50f3762f8c50e5a4d Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 4 Jun 2022 22:06:04 +0800 Subject: [PATCH 72/93] Convert DataUtils.ERROR_UNSUPPORTED_FORMAT to ErrorCode.FILE_VERSION_ERROR_1 --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/mvstore/db/Store.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index a20b64af31..2628451f85 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 #3468: Invalid DB format exception (for 1.x DB in 2.x h2) should have a specific SQLException vendorCode +
                    • Issue #3528: Weird syntax error with HAVING clause in Oracle Mode
                    • Issue #3307: Fix SHUTDOWN DEFRAG for encrypted databases diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index 2ef675185a..fd1335d95e 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -92,6 +92,7 @@ public Store(Database db, byte[] key) { boolean encrypted = false; if (dbPath != null) { String fileName = dbPath + Constants.SUFFIX_MV_FILE; + this.fileName = fileName; MVStoreTool.compactCleanUp(fileName); builder.fileName(fileName); builder.pageSplitSize(db.getPageSize()); @@ -126,12 +127,12 @@ public Store(Database db, byte[] key) { // otherwise background thread would compete for store lock // with maps opening procedure builder.autoCommitDisabled(); + } else { + fileName = null; } this.encrypted = encrypted; try { this.mvStore = builder.open(); - FileStore fs = mvStore.getFileStore(); - fileName = fs != null ? fs.getFileName() : null; if (!db.getSettings().reuseSpace) { mvStore.setReuseSpace(false); } @@ -155,6 +156,8 @@ DbException convertMVStoreException(MVStoreException e) { switch (e.getErrorCode()) { case DataUtils.ERROR_CLOSED: throw DbException.get(ErrorCode.DATABASE_IS_CLOSED, e, fileName); + case DataUtils.ERROR_UNSUPPORTED_FORMAT: + throw DbException.get(ErrorCode.FILE_VERSION_ERROR_1, e, fileName); case DataUtils.ERROR_FILE_CORRUPT: if (encrypted) { throw DbException.get(ErrorCode.FILE_ENCRYPTION_ERROR_1, e, fileName); From d8d4b5d57e69b7cb00dff2fc5abc530e7aa747ed Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 4 Jun 2022 22:23:33 +0800 Subject: [PATCH 73/93] Close result sets from Java table value functions --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/main/org/h2/schema/FunctionAlias.java | 6 ++--- h2/src/test/org/h2/test/db/TestFunctions.java | 22 +++++++++++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 2628451f85..b600ef7e24 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 #3434: JavaTableFunction is not closing underlying ResultSet when reading column list +
                      • Issue #3468: Invalid DB format exception (for 1.x DB in 2.x h2) should have a specific SQLException vendorCode
                      • Issue #3528: Weird syntax error with HAVING clause in Oracle Mode diff --git a/h2/src/main/org/h2/schema/FunctionAlias.java b/h2/src/main/org/h2/schema/FunctionAlias.java index 47caf1ecf9..c5fccd968a 100644 --- a/h2/src/main/org/h2/schema/FunctionAlias.java +++ b/h2/src/main/org/h2/schema/FunctionAlias.java @@ -374,13 +374,13 @@ public ResultInterface getTableValue(SessionLocal session, Expression[] args, bo * Create a result for the given result set. * * @param session the session - * @param rs the result set + * @param resultSet the result set * @param maxrows the maximum number of rows to read (0 to just read the * meta data) * @return the value */ - public static ResultInterface resultSetToResult(SessionLocal session, ResultSet rs, int maxrows) { - try { + public static ResultInterface resultSetToResult(SessionLocal session, ResultSet resultSet, int maxrows) { + try (ResultSet rs = resultSet) { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); Expression[] columns = new Expression[columnCount]; diff --git a/h2/src/test/org/h2/test/db/TestFunctions.java b/h2/src/test/org/h2/test/db/TestFunctions.java index cb2521a71e..30c66e9961 100644 --- a/h2/src/test/org/h2/test/db/TestFunctions.java +++ b/h2/src/test/org/h2/test/db/TestFunctions.java @@ -44,6 +44,7 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.HashSet; import java.util.Locale; import java.util.Properties; import java.util.TimeZone; @@ -78,6 +79,8 @@ public class TestFunctions extends TestDb implements AggregateFunction { static int count; + private static HashSet RESULT_SETS = new HashSet<>(); + /** * Run just this test. * @@ -157,12 +160,21 @@ private void testVersion() throws SQLException { private void testFunctionTable() throws SQLException { Connection conn = getConnection("functions"); Statement stat = conn.createStatement(); - stat.execute("create alias simple_function_table for '" + - TestFunctions.class.getName() + ".simpleFunctionTable'"); + synchronized (RESULT_SETS) { + try { + stat.execute("create alias simple_function_table for '" + + TestFunctions.class.getName() + ".simpleFunctionTable'"); + stat.execute("select * from simple_function_table() " + + "where a>0 and b in ('x', 'y')"); + for (SimpleResultSet rs : RESULT_SETS) { + assertTrue(rs.isClosed()); + } + } finally { + RESULT_SETS.clear(); + } + } stat.execute("create alias function_table_with_parameter for '" + TestFunctions.class.getName() + ".functionTableWithParameter'"); - stat.execute("select * from simple_function_table() " + - "where a>0 and b in ('x', 'y')"); PreparedStatement prep = conn.prepareStatement("call function_table_with_parameter(?)"); prep.setInt(1, 10); ResultSet rs = prep.executeQuery(); @@ -197,6 +209,8 @@ public static ResultSet simpleFunctionTable(@SuppressWarnings("unused") Connecti result.addColumn("A", Types.INTEGER, 0, 0); result.addColumn("B", Types.CHAR, 0, 0); result.addRow(42, 'X'); + result.setAutoClose(false); + RESULT_SETS.add(result); return result; } From c587ffa3c738dec6706a6d702249ea8091302f67 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 4 Jun 2022 23:37:56 +0800 Subject: [PATCH 74/93] Quote identifiers in linked tables when possible --- h2/src/docsrc/html/changelog.html | 2 + h2/src/main/org/h2/index/LinkedIndex.java | 38 +++++++++++++++++-- h2/src/main/org/h2/table/TableLink.java | 11 ++++++ .../test/org/h2/test/db/TestLinkedTable.java | 25 ++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index b600ef7e24..23bd7bc41f 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 #3448: With linked table to postgreSQL, case-sensitive column names not respected in where part +
                        • Issue #3434: JavaTableFunction is not closing underlying ResultSet when reading column list
                        • Issue #3468: Invalid DB format exception (for 1.x DB in 2.x h2) should have a specific SQLException vendorCode diff --git a/h2/src/main/org/h2/index/LinkedIndex.java b/h2/src/main/org/h2/index/LinkedIndex.java index b5b9a00914..07c3bf95ca 100644 --- a/h2/src/main/org/h2/index/LinkedIndex.java +++ b/h2/src/main/org/h2/index/LinkedIndex.java @@ -97,7 +97,7 @@ public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { builder.append(f ? " AND " : " WHERE "); f = true; Column col = table.getColumn(i); - col.getSQL(builder, sqlFlags); + addColumnName(builder, col); if (v == ValueNull.INSTANCE) { builder.append(" IS NULL"); } else { @@ -113,7 +113,7 @@ public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { builder.append(f ? " AND " : " WHERE "); f = true; Column col = table.getColumn(i); - col.getSQL(builder, sqlFlags); + addColumnName(builder, col); if (v == ValueNull.INSTANCE) { builder.append(" IS NULL"); } else { @@ -133,6 +133,36 @@ public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { } } + private void addColumnName(StringBuilder builder, Column col) { + String identifierQuoteString = link.getIdentifierQuoteString(); + String name = col.getName(); + if (identifierQuoteString == null || identifierQuoteString.isEmpty() || identifierQuoteString.equals(" ")) { + builder.append(name); + } else if (identifierQuoteString.equals("\"")) { + /* + * StringUtils.quoteIdentifier() can produce Unicode identifiers, + * but target DBMS isn't required to support them + */ + builder.append('"'); + int i = name.indexOf('"'); + if (i < 0) { + builder.append(name); + } else { + builder.append(name, 0, ++i).append('"'); + for (int l = name.length(); i < l; i++) { + char c = name.charAt(i); + if (c == '"') { + builder.append('"'); + } + builder.append(c); + } + } + builder.append('"'); + } else { + builder.append(identifierQuoteString).append(name).append(identifierQuoteString); + } + } + private void addParameter(StringBuilder builder, Column col) { TypeInfo type = col.getType(); if (type.getValueType() == Value.CHAR && link.isOracle()) { @@ -183,7 +213,7 @@ public void remove(SessionLocal session, Row row) { builder.append("AND "); } Column col = table.getColumn(i); - col.getSQL(builder, sqlFlags); + addColumnName(builder, col); Value v = row.getValue(i); if (isNull(v)) { builder.append(" IS NULL "); @@ -235,7 +265,7 @@ public void update(Row oldRow, Row newRow, SessionLocal session) { if (i > 0) { builder.append(" AND "); } - col.getSQL(builder, sqlFlags); + addColumnName(builder, col); Value v = oldRow.getValue(i); if (isNull(v)) { builder.append(" IS NULL"); diff --git a/h2/src/main/org/h2/table/TableLink.java b/h2/src/main/org/h2/table/TableLink.java index ca34042e66..dc46b21732 100644 --- a/h2/src/main/org/h2/table/TableLink.java +++ b/h2/src/main/org/h2/table/TableLink.java @@ -62,6 +62,7 @@ public class TableLink extends Table { private boolean storesMixedCase; private boolean storesMixedCaseQuoted; private boolean supportsMixedCaseIdentifiers; + private String identifierQuoteString; private boolean globalTemporary; private boolean readOnly; private final boolean targetsMySql; @@ -125,6 +126,7 @@ private void readMetaData() throws SQLException { storesMixedCase = meta.storesMixedCaseIdentifiers(); storesMixedCaseQuoted = meta.storesMixedCaseQuotedIdentifiers(); supportsMixedCaseIdentifiers = meta.supportsMixedCaseIdentifiers(); + identifierQuoteString = meta.getIdentifierQuoteString(); ArrayList columnList = Utils.newSmallArrayList(); HashMap columnMap = new HashMap<>(); String schema = null; @@ -737,4 +739,13 @@ public int getFetchSize() { return fetchSize; } + /** + * Returns the identifier quote string or space. + * + * @return the identifier quote string or space + */ + public String getIdentifierQuoteString() { + return identifierQuoteString; + } + } diff --git a/h2/src/test/org/h2/test/db/TestLinkedTable.java b/h2/src/test/org/h2/test/db/TestLinkedTable.java index d33f137c67..c398928975 100644 --- a/h2/src/test/org/h2/test/db/TestLinkedTable.java +++ b/h2/src/test/org/h2/test/db/TestLinkedTable.java @@ -54,6 +54,7 @@ public void test() throws SQLException { testGeometry(); testFetchSize(); testFetchSizeWithAutoCommit(); + testQuotedIdentifiers(); deleteDb("linkedTable"); } @@ -774,4 +775,28 @@ private void testFetchSizeWithAutoCommit() throws SQLException { cb.close(); } + private void testQuotedIdentifiers() throws SQLException { + if (config.memory) { + return; + } + org.h2.Driver.load(); + Connection ca = DriverManager.getConnection("jdbc:h2:mem:one", "sa", "sa"); + Connection cb = DriverManager.getConnection("jdbc:h2:mem:two", "sa", "sa"); + Statement sa = ca.createStatement(); + Statement sb = cb.createStatement(); + sa.execute("CREATE TABLE \"Test\" AS SELECT X \"Num\", X \"Column \"\"1\"\"\" FROM SYSTEM_RANGE(1, 100)"); + sb.execute("CREATE LINKED TABLE T(NULL, 'jdbc:h2:mem:one', 'sa', 'sa', '\"Test\"')"); + try (ResultSet rs = sb.executeQuery("SELECT SUM(\"Num\") FROM T WHERE \"Num\" > 1")) { + assertTrue(rs.next()); + assertEquals(5049, rs.getInt(1)); + } + try (ResultSet rs = sb.executeQuery( + "SELECT SUM(\"Column \"\"1\"\"\") FROM T WHERE \"Column \"\"1\"\"\" > 1")) { + assertTrue(rs.next()); + assertEquals(5049, rs.getInt(1)); + } + ca.close(); + cb.close(); + } + } From edbad9d9cb88416ca1cc587291b221374a908bba Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 14:37:33 +0800 Subject: [PATCH 75/93] Fix synthetic access --- h2/src/main/org/h2/mvstore/db/LobStorageMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index 02ab3005fa..ac4624e871 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -58,7 +58,7 @@ public final class LobStorageMap implements LobStorageInterface private static final boolean TRACE = false; private final Database database; - private final MVStore mvStore; + final MVStore mvStore; private final AtomicLong nextLobId = new AtomicLong(0); private final ThreadPoolExecutor cleanupExecutor; From 2f863ebc4cf4995ebe5c172142fd7d2bda857dec Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 14:38:33 +0800 Subject: [PATCH 76/93] Remove unused imports --- h2/src/main/org/h2/engine/Database.java | 1 - h2/src/main/org/h2/mvstore/db/MVTempResult.java | 1 - h2/src/main/org/h2/server/web/WebThread.java | 1 - 3 files changed, 3 deletions(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 3edb33450b..28aa24d2ab 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -54,7 +54,6 @@ import org.h2.store.FileLockMethod; import org.h2.store.FileStore; import org.h2.store.InDoubtTransaction; -import org.h2.store.LobStorageFrontend; import org.h2.store.LobStorageInterface; import org.h2.store.fs.FileUtils; import org.h2.store.fs.encrypt.FileEncrypt; diff --git a/h2/src/main/org/h2/mvstore/db/MVTempResult.java b/h2/src/main/org/h2/mvstore/db/MVTempResult.java index 5618d7e79b..53ec8e0a2a 100644 --- a/h2/src/main/org/h2/mvstore/db/MVTempResult.java +++ b/h2/src/main/org/h2/mvstore/db/MVTempResult.java @@ -15,7 +15,6 @@ import org.h2.message.DbException; import org.h2.mvstore.FileStore; import org.h2.mvstore.MVStore; -import org.h2.mvstore.MVStore.Builder; import org.h2.result.ResultExternal; import org.h2.result.SortOrder; import org.h2.store.fs.FileUtils; diff --git a/h2/src/main/org/h2/server/web/WebThread.java b/h2/src/main/org/h2/server/web/WebThread.java index 2c6a7fd6b5..41f55206f4 100644 --- a/h2/src/main/org/h2/server/web/WebThread.java +++ b/h2/src/main/org/h2/server/web/WebThread.java @@ -9,7 +9,6 @@ import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; From 3d67916a645a312d9baa5ca13ece576241d42b2c Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 15:10:49 +0800 Subject: [PATCH 77/93] Change spatial parameters from Object to Spatial --- .../main/org/h2/mvstore/rtree/MVRTreeMap.java | 22 +-- .../org/h2/mvstore/rtree/SpatialDataType.java | 128 ++++++++---------- 2 files changed, 66 insertions(+), 84 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java index b856e720b5..6464cfa843 100644 --- a/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java +++ b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java @@ -69,7 +69,7 @@ public RTreeCursor findContainedKeys(Spatial x) { return new ContainsRTreeCursor<>(getRootPage(), x, keyType); } - private boolean contains(Page p, int index, Object key) { + private boolean contains(Page p, int index, Spatial key) { return keyType.contains(p.getKey(index), key); } @@ -236,7 +236,7 @@ private V operate(Page p, Spatial key, V value, DecisionMaker split(Page p) { private Page splitLinear(Page p) { int keyCount = p.getKeyCount(); - ArrayList keys = new ArrayList<>(keyCount); + ArrayList keys = new ArrayList<>(keyCount); for (int i = 0; i < keyCount; i++) { keys.add(p.getKey(i)); } @@ -323,10 +323,10 @@ private Page splitLinear(Page p) { extremes[1]--; } move(p, splitB, extremes[1]); - Object boundsA = keyType.createBoundingBox(splitA.getKey(0)); - Object boundsB = keyType.createBoundingBox(splitB.getKey(0)); + Spatial boundsA = keyType.createBoundingBox(splitA.getKey(0)); + Spatial boundsB = keyType.createBoundingBox(splitB.getKey(0)); while (p.getKeyCount() > 0) { - Object o = p.getKey(0); + Spatial o = p.getKey(0); float a = keyType.getAreaIncrease(boundsA, o); float b = keyType.getAreaIncrease(boundsB, o); if (a < b) { @@ -350,12 +350,12 @@ private Page splitQuadratic(Page p) { int ia = 0, ib = 0; int keyCount = p.getKeyCount(); for (int a = 0; a < keyCount; a++) { - Object objA = p.getKey(a); + Spatial objA = p.getKey(a); for (int b = 0; b < keyCount; b++) { if (a == b) { continue; } - Object objB = p.getKey(b); + Spatial objB = p.getKey(b); float area = keyType.getCombinedArea(objA, objB); if (area > largest) { largest = area; @@ -369,14 +369,14 @@ private Page splitQuadratic(Page p) { ib--; } move(p, splitB, ib); - Object boundsA = keyType.createBoundingBox(splitA.getKey(0)); - Object boundsB = keyType.createBoundingBox(splitB.getKey(0)); + Spatial boundsA = keyType.createBoundingBox(splitA.getKey(0)); + Spatial boundsB = keyType.createBoundingBox(splitB.getKey(0)); while (p.getKeyCount() > 0) { float diff = 0, bestA = 0, bestB = 0; int best = 0; keyCount = p.getKeyCount(); for (int i = 0; i < keyCount; i++) { - Object o = p.getKey(i); + Spatial o = p.getKey(i); float incA = keyType.getAreaIncrease(boundsA, o); float incB = keyType.getAreaIncrease(boundsB, o); float d = Math.abs(incA - incB); diff --git a/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java b/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java index 6af8a5887e..2f1be678d3 100644 --- a/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java +++ b/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java @@ -68,15 +68,13 @@ public int compare(Spatial a, Spatial b) { * @param b the second value * @return true if they are equal */ - public boolean equals(Object a, Object b) { + public boolean equals(Spatial a, Spatial b) { if (a == b) { return true; } else if (a == null || b == null) { return false; } - long la = ((Spatial) a).getId(); - long lb = ((Spatial) b).getId(); - return la == lb; + return a.getId() == b.getId(); } @Override @@ -155,20 +153,18 @@ public boolean isOverlap(Spatial a, Spatial b) { * @param bounds the bounds (may be modified) * @param add the value */ - public void increaseBounds(Object bounds, Object add) { - Spatial a = (Spatial) add; - Spatial b = (Spatial) bounds; - if (a.isNull() || b.isNull()) { + public void increaseBounds(Spatial bounds, Spatial add) { + if (add.isNull() || bounds.isNull()) { return; } for (int i = 0; i < dimensions; i++) { - float v = a.min(i); - if (v < b.min(i)) { - b.setMin(i, v); + float v = add.min(i); + if (v < bounds.min(i)) { + bounds.setMin(i, v); } - v = a.max(i); - if (v > b.max(i)) { - b.setMax(i, v); + v = add.max(i); + if (v > bounds.max(i)) { + bounds.setMax(i, v); } } } @@ -176,28 +172,26 @@ public void increaseBounds(Object bounds, Object add) { /** * Get the area increase by extending a to contain b. * - * @param objA the bounding box - * @param objB the object + * @param bounds the bounding box + * @param add the object * @return the area */ - public float getAreaIncrease(Object objA, Object objB) { - Spatial b = (Spatial) objB; - Spatial a = (Spatial) objA; - if (a.isNull() || b.isNull()) { + public float getAreaIncrease(Spatial bounds, Spatial add) { + if (bounds.isNull() || add.isNull()) { return 0; } - float min = a.min(0); - float max = a.max(0); + float min = bounds.min(0); + float max = bounds.max(0); float areaOld = max - min; - min = Math.min(min, b.min(0)); - max = Math.max(max, b.max(0)); + min = Math.min(min, add.min(0)); + max = Math.max(max, add.max(0)); float areaNew = max - min; for (int i = 1; i < dimensions; i++) { - min = a.min(i); - max = a.max(i); + min = bounds.min(i); + max = bounds.max(i); areaOld *= max - min; - min = Math.min(min, b.min(i)); - max = Math.max(max, b.max(i)); + min = Math.min(min, add.min(i)); + max = Math.max(max, add.max(i)); areaNew *= max - min; } return areaNew - areaOld; @@ -206,13 +200,11 @@ public float getAreaIncrease(Object objA, Object objB) { /** * Get the combined area of both objects. * - * @param objA the first object - * @param objB the second object + * @param a the first object + * @param b the second object * @return the area */ - float getCombinedArea(Object objA, Object objB) { - Spatial a = (Spatial) objA; - Spatial b = (Spatial) objB; + float getCombinedArea(Spatial a, Spatial b) { if (a.isNull()) { return getArea(b); } else if (b.isNull()) { @@ -239,20 +231,18 @@ private float getArea(Spatial a) { } /** - * Check whether a contains b. + * Check whether bounds contains object. * - * @param objA the bounding box - * @param objB the object + * @param bounds the bounding box + * @param object the object * @return the area */ - public boolean contains(Object objA, Object objB) { - Spatial a = (Spatial) objA; - Spatial b = (Spatial) objB; - if (a.isNull() || b.isNull()) { + public boolean contains(Spatial bounds, Spatial object) { + if (bounds.isNull() || object.isNull()) { return false; } for (int i = 0; i < dimensions; i++) { - if (a.min(i) > b.min(i) || a.max(i) < b.max(i)) { + if (bounds.min(i) > object.min(i) || bounds.max(i) < object.max(i)) { return false; } } @@ -260,21 +250,18 @@ public boolean contains(Object objA, Object objB) { } /** - * Check whether a is completely inside b and does not touch the - * given bound. + * Check whether object is completely inside bounds and does not touch them. * - * @param objA the object to check - * @param objB the bounds + * @param object the object to check + * @param bounds the bounds * @return true if a is completely inside b */ - public boolean isInside(Object objA, Object objB) { - Spatial a = (Spatial) objA; - Spatial b = (Spatial) objB; - if (a.isNull() || b.isNull()) { + public boolean isInside(Spatial object, Spatial bounds) { + if (object.isNull() || bounds.isNull()) { return false; } for (int i = 0; i < dimensions; i++) { - if (a.min(i) <= b.min(i) || a.max(i) >= b.max(i)) { + if (object.min(i) <= bounds.min(i) || object.max(i) >= bounds.max(i)) { return false; } } @@ -284,15 +271,14 @@ public boolean isInside(Object objA, Object objB) { /** * Create a bounding box starting with the given object. * - * @param objA the object + * @param object the object * @return the bounding box */ - Spatial createBoundingBox(Object objA) { - Spatial a = (Spatial) objA; - if (a.isNull()) { - return a; + Spatial createBoundingBox(Spatial object) { + if (object.isNull()) { + return object; } - return a.clone(0); + return object.clone(0); } /** @@ -303,7 +289,7 @@ Spatial createBoundingBox(Object objA) { * @param list the objects * @return the indexes of the extremes */ - public int[] getExtremes(ArrayList list) { + public int[] getExtremes(ArrayList list) { list = getNotNull(list); if (list.isEmpty()) { return null; @@ -315,7 +301,7 @@ public int[] getExtremes(ArrayList list) { boundsInner.setMin(i, boundsInner.max(i)); boundsInner.setMax(i, t); } - for (Object o : list) { + for (Spatial o : list) { increaseBounds(bounds, o); increaseMaxInnerBounds(boundsInner, o); } @@ -341,7 +327,7 @@ public int[] getExtremes(ArrayList list) { int firstIndex = -1, lastIndex = -1; for (int i = 0; i < list.size() && (firstIndex < 0 || lastIndex < 0); i++) { - Spatial o = (Spatial) list.get(i); + Spatial o = list.get(i); if (firstIndex < 0 && o.max(bestDim) == min) { firstIndex = i; } else if (lastIndex < 0 && o.min(bestDim) == max) { @@ -351,11 +337,10 @@ public int[] getExtremes(ArrayList list) { return new int[] { firstIndex, lastIndex }; } - private static ArrayList getNotNull(ArrayList list) { + private static ArrayList getNotNull(ArrayList list) { boolean foundNull = false; - for (Object o : list) { - Spatial a = (Spatial) o; - if (a.isNull()) { + for (Spatial o : list) { + if (o.isNull()) { foundNull = true; break; } @@ -363,22 +348,19 @@ private static ArrayList getNotNull(ArrayList list) { if (!foundNull) { return list; } - ArrayList result = new ArrayList<>(); - for (Object o : list) { - Spatial a = (Spatial) o; - if (!a.isNull()) { - result.add(a); + ArrayList result = new ArrayList<>(); + for (Spatial o : list) { + if (!o.isNull()) { + result.add(o); } } return result; } - private void increaseMaxInnerBounds(Object bounds, Object add) { - Spatial b = (Spatial) bounds; - Spatial a = (Spatial) add; + private void increaseMaxInnerBounds(Spatial bounds, Spatial add) { for (int i = 0; i < dimensions; i++) { - b.setMin(i, Math.min(b.min(i), a.max(i))); - b.setMax(i, Math.max(b.max(i), a.min(i))); + bounds.setMin(i, Math.min(bounds.min(i), add.max(i))); + bounds.setMax(i, Math.max(bounds.max(i), add.min(i))); } } From 9bbb5056768849b30ed4aef1b72103042299712e Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 15:45:31 +0800 Subject: [PATCH 78/93] Add default implementation of DbObject.getCreateSQLForCopy() --- h2/src/main/org/h2/constraint/ConstraintDomain.java | 5 ----- h2/src/main/org/h2/engine/Comment.java | 6 ------ h2/src/main/org/h2/engine/DbObject.java | 4 +++- h2/src/main/org/h2/engine/Role.java | 7 ------- h2/src/main/org/h2/engine/Setting.java | 6 ------ h2/src/main/org/h2/engine/User.java | 5 ----- h2/src/main/org/h2/schema/Constant.java | 7 ------- h2/src/main/org/h2/schema/Domain.java | 7 ------- h2/src/main/org/h2/schema/Schema.java | 5 ----- h2/src/main/org/h2/schema/Sequence.java | 6 ------ h2/src/main/org/h2/schema/UserDefinedFunction.java | 6 ------ h2/src/main/org/h2/table/Table.java | 5 ----- 12 files changed, 3 insertions(+), 66 deletions(-) diff --git a/h2/src/main/org/h2/constraint/ConstraintDomain.java b/h2/src/main/org/h2/constraint/ConstraintDomain.java index c866c808bb..c0d825045f 100644 --- a/h2/src/main/org/h2/constraint/ConstraintDomain.java +++ b/h2/src/main/org/h2/constraint/ConstraintDomain.java @@ -75,11 +75,6 @@ public void setExpression(SessionLocal session, Expression expr) { this.expr = expr; } - @Override - public String getCreateSQLForCopy(Table forTable, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getCreateSQLWithoutIndexes() { return getCreateSQL(); diff --git a/h2/src/main/org/h2/engine/Comment.java b/h2/src/main/org/h2/engine/Comment.java index e3af80fb67..d8d66ac691 100644 --- a/h2/src/main/org/h2/engine/Comment.java +++ b/h2/src/main/org/h2/engine/Comment.java @@ -7,7 +7,6 @@ import org.h2.message.DbException; import org.h2.message.Trace; -import org.h2.table.Table; import org.h2.util.StringUtils; /** @@ -25,11 +24,6 @@ public Comment(Database database, int id, DbObject obj) { this.quotedObjectName = obj.getSQL(DEFAULT_SQL_FLAGS); } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - private static String getTypeName(int type) { switch (type) { case DbObject.CONSTANT: diff --git a/h2/src/main/org/h2/engine/DbObject.java b/h2/src/main/org/h2/engine/DbObject.java index 7464f97794..38c0bad802 100644 --- a/h2/src/main/org/h2/engine/DbObject.java +++ b/h2/src/main/org/h2/engine/DbObject.java @@ -228,7 +228,9 @@ public final boolean isValid() { * @param quotedName the quoted name * @return the SQL statement */ - public abstract String getCreateSQLForCopy(Table table, String quotedName); + public String getCreateSQLForCopy(Table table, String quotedName) { + throw DbException.getInternalError(toString()); + } /** * Construct the CREATE ... SQL statement for this object for meta table. diff --git a/h2/src/main/org/h2/engine/Role.java b/h2/src/main/org/h2/engine/Role.java index 7fec06ca11..e7364814fd 100644 --- a/h2/src/main/org/h2/engine/Role.java +++ b/h2/src/main/org/h2/engine/Role.java @@ -7,10 +7,8 @@ import java.util.ArrayList; -import org.h2.message.DbException; import org.h2.message.Trace; import org.h2.schema.Schema; -import org.h2.table.Table; /** * Represents a role. Roles can be granted to users, and to other roles. @@ -24,11 +22,6 @@ public Role(Database database, int id, String roleName, boolean system) { this.system = system; } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - /** * Get the CREATE SQL statement for this object. * diff --git a/h2/src/main/org/h2/engine/Setting.java b/h2/src/main/org/h2/engine/Setting.java index 3d8cc24576..d2de710aa4 100644 --- a/h2/src/main/org/h2/engine/Setting.java +++ b/h2/src/main/org/h2/engine/Setting.java @@ -7,7 +7,6 @@ import org.h2.message.DbException; import org.h2.message.Trace; -import org.h2.table.Table; /** * A persistent database setting. @@ -47,11 +46,6 @@ public String getStringValue() { return stringValue; } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getCreateSQL() { StringBuilder buff = new StringBuilder("SET "); diff --git a/h2/src/main/org/h2/engine/User.java b/h2/src/main/org/h2/engine/User.java index 281d691ec1..80e1c3c4b5 100644 --- a/h2/src/main/org/h2/engine/User.java +++ b/h2/src/main/org/h2/engine/User.java @@ -74,11 +74,6 @@ public void setUserPasswordHash(byte[] userPasswordHash) { } } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getCreateSQL() { return getCreateSQL(true); diff --git a/h2/src/main/org/h2/schema/Constant.java b/h2/src/main/org/h2/schema/Constant.java index bcf523ab79..c7feff95e3 100644 --- a/h2/src/main/org/h2/schema/Constant.java +++ b/h2/src/main/org/h2/schema/Constant.java @@ -8,9 +8,7 @@ import org.h2.engine.DbObject; import org.h2.engine.SessionLocal; import org.h2.expression.ValueExpression; -import org.h2.message.DbException; import org.h2.message.Trace; -import org.h2.table.Table; import org.h2.value.Value; /** @@ -26,11 +24,6 @@ public Constant(Schema schema, int id, String name) { super(schema, id, name, Trace.SCHEMA); } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getCreateSQL() { StringBuilder builder = new StringBuilder("CREATE CONSTANT "); diff --git a/h2/src/main/org/h2/schema/Domain.java b/h2/src/main/org/h2/schema/Domain.java index 1003a2105a..297ecf301e 100644 --- a/h2/src/main/org/h2/schema/Domain.java +++ b/h2/src/main/org/h2/schema/Domain.java @@ -12,10 +12,8 @@ import org.h2.engine.SessionLocal; import org.h2.expression.Expression; import org.h2.expression.ValueExpression; -import org.h2.message.DbException; import org.h2.message.Trace; import org.h2.table.ColumnTemplate; -import org.h2.table.Table; import org.h2.util.Utils; import org.h2.value.TypeInfo; import org.h2.value.Value; @@ -42,11 +40,6 @@ public Domain(Schema schema, int id, String name) { super(schema, id, name, Trace.SCHEMA); } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getDropSQL() { StringBuilder builder = new StringBuilder("DROP DOMAIN IF EXISTS "); diff --git a/h2/src/main/org/h2/schema/Schema.java b/h2/src/main/org/h2/schema/Schema.java index 9002a5c8a9..bb508605f3 100644 --- a/h2/src/main/org/h2/schema/Schema.java +++ b/h2/src/main/org/h2/schema/Schema.java @@ -92,11 +92,6 @@ public boolean canDrop() { return !system; } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getCreateSQL() { if (system) { diff --git a/h2/src/main/org/h2/schema/Sequence.java b/h2/src/main/org/h2/schema/Sequence.java index f21b918132..3dceeda1da 100644 --- a/h2/src/main/org/h2/schema/Sequence.java +++ b/h2/src/main/org/h2/schema/Sequence.java @@ -11,7 +11,6 @@ import org.h2.engine.SessionLocal; import org.h2.message.DbException; import org.h2.message.Trace; -import org.h2.table.Table; import org.h2.value.TypeInfo; import org.h2.value.Value; import org.h2.value.ValueBigint; @@ -350,11 +349,6 @@ public String getDropSQL() { return getSQL(builder, DEFAULT_SQL_FLAGS).toString(); } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public String getCreateSQL() { StringBuilder builder = getSQL(new StringBuilder("CREATE SEQUENCE "), DEFAULT_SQL_FLAGS); diff --git a/h2/src/main/org/h2/schema/UserDefinedFunction.java b/h2/src/main/org/h2/schema/UserDefinedFunction.java index 7a3c6c8954..f697f0ed32 100644 --- a/h2/src/main/org/h2/schema/UserDefinedFunction.java +++ b/h2/src/main/org/h2/schema/UserDefinedFunction.java @@ -6,7 +6,6 @@ package org.h2.schema; import org.h2.message.DbException; -import org.h2.table.Table; /** * User-defined Java function or aggregate function. @@ -19,11 +18,6 @@ public abstract class UserDefinedFunction extends SchemaObject { super(newSchema, id, name, traceModuleId); } - @Override - public final String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - @Override public final void checkRename() { throw DbException.getUnsupportedException("RENAME"); diff --git a/h2/src/main/org/h2/table/Table.java b/h2/src/main/org/h2/table/Table.java index d194c64015..92974e8781 100644 --- a/h2/src/main/org/h2/table/Table.java +++ b/h2/src/main/org/h2/table/Table.java @@ -401,11 +401,6 @@ public Column getRowIdColumn() { return null; } - @Override - public String getCreateSQLForCopy(Table table, String quotedName) { - throw DbException.getInternalError(toString()); - } - /** * Check whether the table (or view) contains no columns that prevent index * conditions to be used. For example, a view that contains the ROWNUM() From 1ac7a24a092bc0e8dc654046f0b134ab2f857ac1 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 16:25:25 +0800 Subject: [PATCH 79/93] Require ADMIN privileges for tables with table engines --- h2/src/main/org/h2/command/ddl/CreateTable.java | 7 +++++-- h2/src/main/org/h2/res/help.csv | 4 ++++ .../test/org/h2/test/db/TestTableEngines.java | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/h2/src/main/org/h2/command/ddl/CreateTable.java b/h2/src/main/org/h2/command/ddl/CreateTable.java index 213b178702..ede7e6ce96 100644 --- a/h2/src/main/org/h2/command/ddl/CreateTable.java +++ b/h2/src/main/org/h2/command/ddl/CreateTable.java @@ -71,10 +71,13 @@ public void setIfNotExists(boolean ifNotExists) { public long update() { Schema schema = getSchema(); boolean isSessionTemporary = data.temporary && !data.globalTemporary; - if (!isSessionTemporary) { + Database db = session.getDatabase(); + String tableEngine = data.tableEngine; + if (tableEngine != null || db.getSettings().defaultTableEngine != null) { + session.getUser().checkAdmin(); + } else if (!isSessionTemporary) { session.getUser().checkSchemaOwner(schema); } - Database db = session.getDatabase(); if (!db.isPersistent()) { data.persistIndexes = false; } diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 791a92bc82..6d6026aec0 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -961,6 +961,10 @@ TABLE @h2@ [ IF NOT EXISTS ] [schemaName.]tableName [ AS ( query ) [ WITH [ NO ] DATA ] ]"," Creates a new table. +Admin rights are required to execute this command +if and only if ENGINE option is used or custom default table engine is configured in the database. +Schema owner rights or ALTER ANY SCHEMA rights are required for creation of regular tables and GLOBAL TEMPORARY tables. + Cached tables (the default for regular tables) are persistent, and the number of rows is not limited by the main memory. Memory tables (the default for temporary tables) are persistent, diff --git a/h2/src/test/org/h2/test/db/TestTableEngines.java b/h2/src/test/org/h2/test/db/TestTableEngines.java index a87646f7e3..3afd8cb47c 100644 --- a/h2/src/test/org/h2/test/db/TestTableEngines.java +++ b/h2/src/test/org/h2/test/db/TestTableEngines.java @@ -19,6 +19,7 @@ import java.util.Set; import java.util.TreeSet; +import org.h2.api.ErrorCode; import org.h2.api.TableEngine; import org.h2.command.ddl.CreateTableData; import org.h2.command.query.AllColumnsForPlan; @@ -60,6 +61,7 @@ public static void main(String[] a) throws Exception { @Override public void test() throws Exception { + testAdminPrivileges(); testQueryExpressionFlag(); testSubQueryInfo(); testEngineParams(); @@ -68,6 +70,21 @@ public void test() throws Exception { testMultiColumnTreeSetIndex(); } + private void testAdminPrivileges() throws SQLException { + deleteDb("tableEngine"); + Connection conn = getConnection("tableEngine"); + Statement stat = conn.createStatement(); + stat.execute("CREATE USER U PASSWORD '1'"); + stat.execute("GRANT ALTER ANY SCHEMA TO U"); + Connection connUser = getConnection("tableEngine", "U", "1"); + Statement statUser = connUser.createStatement(); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, statUser) + .execute("CREATE TABLE T(ID INT, NAME VARCHAR) ENGINE \"" + EndlessTableEngine.class.getName() + '"'); + connUser.close(); + conn.close(); + deleteDb("tableEngine"); + } + private void testEngineParams() throws SQLException { deleteDb("tableEngine"); Connection conn = getConnection("tableEngine"); From 98de3b0c5ce461a411b72f4305869a73c5ad447b Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 16:39:08 +0800 Subject: [PATCH 80/93] Avoid generation of too long names in Database.getTempTableName() --- h2/src/main/org/h2/engine/Database.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index 28aa24d2ab..f9d3566eff 100644 --- a/h2/src/main/org/h2/engine/Database.java +++ b/h2/src/main/org/h2/engine/Database.java @@ -1699,10 +1699,13 @@ public Role getPublicRole() { * @return a unique name */ public synchronized String getTempTableName(String baseName, SessionLocal session) { + int maxBaseLength = Constants.MAX_IDENTIFIER_LENGTH - (7 + ValueInteger.DISPLAY_SIZE * 2); + if (baseName.length() > maxBaseLength) { + baseName = baseName.substring(0, maxBaseLength); + } String tempName; do { - tempName = baseName + "_COPY_" + session.getId() + - "_" + nextTempTableId++; + tempName = baseName + "_COPY_" + session.getId() + '_' + nextTempTableId++; } while (mainSchema.findTableOrView(session, tempName) != null); return tempName; } From bc3258692e975725eb2ac5481469391c366dfb23 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 17:37:39 +0800 Subject: [PATCH 81/93] Remove ROUNDMAGIC from documentation --- h2/src/main/org/h2/res/help.csv | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 6d6026aec0..0967c0314e 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -5126,19 +5126,6 @@ This method returns value of the same type as argument, but with adjusted precis ROUND(N, 2) " -"Functions (Numeric)","ROUNDMAGIC"," -@h2@ ROUNDMAGIC(numeric) -"," -This function rounds numbers in a good way, but it is slow. -It has a special handling for numbers around 0. -Only numbers smaller or equal +/-1000000000000 are supported. -The value is converted to a String internally, and then the last 4 characters are checked. -'000x' becomes '0000' and '999x' becomes '999999', which is rounded automatically. -This method returns a double. -"," -ROUNDMAGIC(N/3*3) -" - "Functions (Numeric)","SECURE_RAND"," @h2@ SECURE_RAND(int) "," From a20ed6b6e1f899420087c0bcb2a83922c3c5f218 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 18:07:20 +0800 Subject: [PATCH 82/93] Fix building of documentation --- h2/src/main/org/h2/mvstore/FileStore.java | 6 ++++-- h2/src/main/org/h2/mvstore/db/MVTempResult.java | 3 ++- h2/src/tools/org/h2/build/doc/dictionary.txt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/h2/src/main/org/h2/mvstore/FileStore.java b/h2/src/main/org/h2/mvstore/FileStore.java index 291394dfb4..ef30c36fa9 100644 --- a/h2/src/main/org/h2/mvstore/FileStore.java +++ b/h2/src/main/org/h2/mvstore/FileStore.java @@ -125,8 +125,10 @@ public void writeFully(long pos, ByteBuffer src) { * used */ public void open(String fileName, boolean readOnly, char[] encryptionKey) { - open(fileName, readOnly, encryptionKey == null ? null : - fileChannel -> new FileEncrypt(fileName, FilePathEncrypt.getPasswordBytes(encryptionKey), fileChannel)); + open(fileName, readOnly, + encryptionKey == null ? null + : fileChannel -> new FileEncrypt(fileName, FilePathEncrypt.getPasswordBytes(encryptionKey), + fileChannel)); } public FileStore open(String fileName, boolean readOnly) { diff --git a/h2/src/main/org/h2/mvstore/db/MVTempResult.java b/h2/src/main/org/h2/mvstore/db/MVTempResult.java index 53ec8e0a2a..26735b4fa5 100644 --- a/h2/src/main/org/h2/mvstore/db/MVTempResult.java +++ b/h2/src/main/org/h2/mvstore/db/MVTempResult.java @@ -178,7 +178,8 @@ public static ResultExternal of(Database database, Expression[] expressions, boo String fileName = FileUtils.createTempFile("h2tmp", Constants.SUFFIX_TEMP_FILE, true); FileStore fileStore = database.getStore().getMvStore().getFileStore().open(fileName, false); - MVStore.Builder builder = new MVStore.Builder().adoptFileStore(fileStore).cacheSize(0).autoCommitDisabled(); + MVStore.Builder builder = new MVStore.Builder().adoptFileStore(fileStore).cacheSize(0) + .autoCommitDisabled(); store = builder.open(); this.expressions = expressions; this.visibleColumnCount = visibleColumnCount; diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt index ebc7a03276..3724b9c4cf 100644 --- a/h2/src/tools/org/h2/build/doc/dictionary.txt +++ b/h2/src/tools/org/h2/build/doc/dictionary.txt @@ -849,4 +849,4 @@ entirely skeleton discouraged pearson coefficient squares covariance mytab debug filestore backstop tie breaker lockable lobtx btx waiter accounted aiobe spf resolvers generators abandoned accidental approximately cited competitive configuring drastically happier hasn interactions journal journaling ldt occasional odt officially pragma ration recognising rnrn rough seemed sonatype supplementary subtree ver -wal wbr worse xerial won +wal wbr worse xerial won symlink respected adopted From 5f73ad3d05e2c2024964df209645a8bb3c747f2f Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 18:08:42 +0800 Subject: [PATCH 83/93] Restore support of LTRIM and RTRIM with two arguments --- h2/src/main/org/h2/command/Parser.java | 4 ++-- h2/src/main/org/h2/res/help.csv | 8 ++++---- .../test/org/h2/test/scripts/functions/string/ltrim.sql | 3 +++ .../test/org/h2/test/scripts/functions/string/rtrim.sql | 3 +++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index cc9ce677c7..ffec3ec791 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -4062,9 +4062,9 @@ private Expression readCompatibilityFunction(String name) { return readSubstringFunction(); // TRIM case "LTRIM": - return new TrimFunction(readSingleArgument(), null, TrimFunction.LEADING); + return new TrimFunction(readExpression(), readIfArgument(), TrimFunction.LEADING); case "RTRIM": - return new TrimFunction(readSingleArgument(), null, TrimFunction.TRAILING); + return new TrimFunction(readExpression(), readIfArgument(), TrimFunction.TRAILING); // UPPER case "UCASE": return new StringFunction1(readSingleArgument(), StringFunction1.UPPER); diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv index 0967c0314e..fa6b620fa3 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -5411,9 +5411,9 @@ RPAD(TEXT, 10, '-') " "Functions (String)","LTRIM"," -@c@ LTRIM(string) +@c@ LTRIM(string [, characterToTrimString]) "," -Removes all leading spaces from a string. +Removes all leading spaces or other specified characters from a string. This function is deprecated, use [TRIM](https://h2database.com/html/functions.html#trim) instead of it. "," @@ -5421,9 +5421,9 @@ LTRIM(NAME) " "Functions (String)","RTRIM"," -@c@ RTRIM(string) +@c@ RTRIM(string [, characterToTrimString]) "," -Removes all trailing spaces from a string. +Removes all trailing spaces or other specified characters from a string. This function is deprecated, use [TRIM](https://h2database.com/html/functions.html#trim) instead of it. "," diff --git a/h2/src/test/org/h2/test/scripts/functions/string/ltrim.sql b/h2/src/test/org/h2/test/scripts/functions/string/ltrim.sql index daf8e3e101..ca5732f08a 100644 --- a/h2/src/test/org/h2/test/scripts/functions/string/ltrim.sql +++ b/h2/src/test/org/h2/test/scripts/functions/string/ltrim.sql @@ -8,3 +8,6 @@ select ltrim(null) en, '>' || ltrim('a') || '<' ea, '>' || ltrim(' a ') || '<' e > ---- --- ---- > null >a< >a < > rows: 1 + +VALUES LTRIM('__A__', '_'); +>> A__ diff --git a/h2/src/test/org/h2/test/scripts/functions/string/rtrim.sql b/h2/src/test/org/h2/test/scripts/functions/string/rtrim.sql index a216fd6805..2bab3a06fa 100644 --- a/h2/src/test/org/h2/test/scripts/functions/string/rtrim.sql +++ b/h2/src/test/org/h2/test/scripts/functions/string/rtrim.sql @@ -11,3 +11,6 @@ select rtrim(null) en, '>' || rtrim('a') || '<' ea, '>' || rtrim(' a ') || '<' e select rtrim() from dual; > exception SYNTAX_ERROR_2 + +VALUES RTRIM('__A__', '_'); +>> __A From 1bacc48da7894330bfe1e6913d59c08cef7247b2 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 19:12:26 +0800 Subject: [PATCH 84/93] Improve support for ROW in NON_KEYWORDS list --- h2/src/docsrc/html/changelog.html | 2 + h2/src/main/org/h2/command/Parser.java | 196 +++++++++++++++---------- 2 files changed, 118 insertions(+), 80 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 23bd7bc41f..9a6628ea81 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 #3390: "ROW" cannot be set as a non keyword in 2.x +
                          • Issue #3448: With linked table to postgreSQL, case-sensitive column names not respected in where part
                          • Issue #3434: JavaTableFunction is not closing underlying ResultSet when reading column list diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index ffec3ec791..f158b3dea2 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -1788,8 +1788,7 @@ private void parseValuesForCommand(CommandWithValues command) { do { values.clear(); boolean multiColumn; - if (readIf(ROW)) { - read(OPEN_PAREN); + if (readIf(ROW, OPEN_PAREN)) { multiColumn = true; } else { multiColumn = readIf(OPEN_PAREN); @@ -4968,6 +4967,45 @@ private Parameter readParameter() { } private Expression readTerm() { + Expression r = currentTokenType == IDENTIFIER ? readTermWithIdentifier() : readTermWithoutIdentifier(); + if (readIf(OPEN_BRACKET)) { + r = new ArrayElementReference(r, readExpression()); + read(CLOSE_BRACKET); + } + if (readIf(COLON_COLON)) { + r = readColonColonAfterTerm(r); + } + for (;;) { + TypeInfo ti = readIntervalQualifier(); + if (ti != null) { + r = new CastSpecification(r, ti); + } + int index = tokenIndex; + if (readIf("AT")) { + if (readIf("TIME")) { + read("ZONE"); + r = new TimeZoneOperation(r, readExpression()); + continue; + } else if (readIf("LOCAL")) { + r = new TimeZoneOperation(r, null); + continue; + } else { + setTokenIndex(index); + } + } else if (readIf("FORMAT")) { + if (readIf("JSON")) { + r = new Format(r, FormatEnum.JSON); + continue; + } else { + setTokenIndex(index); + } + } + break; + } + return r; + } + + private Expression readTermWithoutIdentifier() { Expression r; switch (currentTokenType) { case AT: @@ -5064,20 +5102,21 @@ private Expression readTerm() { read(); r = readInterval(); break; - case ROW: { - read(); - read(OPEN_PAREN); - if (readIf(CLOSE_PAREN)) { - r = ValueExpression.get(ValueRow.EMPTY); + case ROW: + if (readIf(ROW, OPEN_PAREN)) { + if (readIf(CLOSE_PAREN)) { + r = ValueExpression.get(ValueRow.EMPTY); + } else { + ArrayList list = Utils.newSmallArrayList(); + do { + list.add(readExpression()); + } while (readIfMore()); + r = new ExpressionList(list.toArray(new Expression[0]), false); + } } else { - ArrayList list = Utils.newSmallArrayList(); - do { - list.add(readExpression()); - } while (readIfMore()); - r = new ExpressionList(list.toArray(new Expression[0]), false); + r = readTermWithIdentifier(); } break; - } case TRUE: read(); r = ValueExpression.TRUE; @@ -5140,17 +5179,21 @@ private Expression readTerm() { break; } case CURRENT_CATALOG: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_CATALOG); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_CATALOG); + break; case CURRENT_DATE: read(); r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_DATE, readIf(OPEN_PAREN), null); break; case CURRENT_PATH: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_PATH); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_PATH); + break; case CURRENT_ROLE: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_ROLE); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_ROLE); + break; case CURRENT_SCHEMA: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_SCHEMA); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_SCHEMA); + break; case CURRENT_TIME: read(); r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIME, readIf(OPEN_PAREN), null); @@ -5162,16 +5205,20 @@ private Expression readTerm() { break; case CURRENT_USER: case USER: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_USER); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_USER); + break; case SESSION_USER: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.SESSION_USER); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.SESSION_USER); + break; case SYSTEM_USER: - return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.SYSTEM_USER); + r = readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.SYSTEM_USER); + break; case ANY: case SOME: read(); read(OPEN_PAREN); - return readAggregate(AggregateType.ANY, "ANY"); + r = readAggregate(AggregateType.ANY, "ANY"); + break; case DAY: case HOUR: case MINUTE: @@ -5218,68 +5265,40 @@ private Expression readTerm() { if (!isIdentifier()) { throw getSyntaxError(); } - //$FALL-THROUGH$ - case IDENTIFIER: - String name = currentToken; - boolean quoted = token.isQuoted(); - read(); - if (readIf(OPEN_PAREN)) { - r = readFunction(null, name); - } else if (readIf(DOT)) { - r = readTermObjectDot(name); - } else if (quoted) { - r = new ExpressionColumn(database, null, null, name); - } else { - r = readTermWithIdentifier(name, quoted); - } + r = readTermWithIdentifier(); break; } - if (readIf(OPEN_BRACKET)) { - r = new ArrayElementReference(r, readExpression()); - read(CLOSE_BRACKET); - } - colonColon: if (readIf(COLON_COLON)) { - if (database.getMode().getEnum() == ModeEnum.PostgreSQL) { - // PostgreSQL compatibility - if (isToken("PG_CATALOG")) { - read("PG_CATALOG"); - read(DOT); - } - if (readIf("REGCLASS")) { - r = new Regclass(r); - break colonColon; - } - } - r = new CastSpecification(r, parseColumnWithType(null)); + return r; + } + + private Expression readTermWithIdentifier() { + Expression r; + String name = currentToken; + boolean quoted = token.isQuoted(); + read(); + if (readIf(OPEN_PAREN)) { + r = readFunction(null, name); + } else if (readIf(DOT)) { + r = readTermObjectDot(name); + } else if (quoted) { + r = new ExpressionColumn(database, null, null, name); + } else { + r = readTermWithIdentifier(name, quoted); } - for (;;) { - TypeInfo ti = readIntervalQualifier(); - if (ti != null) { - r = new CastSpecification(r, ti); + return r; + } + + private Expression readColonColonAfterTerm(Expression r) { + if (database.getMode().getEnum() == ModeEnum.PostgreSQL) { + // PostgreSQL compatibility + if (readIf("PG_CATALOG")) { + read(DOT); } - int index = tokenIndex; - if (readIf("AT")) { - if (readIf("TIME")) { - read("ZONE"); - r = new TimeZoneOperation(r, readExpression()); - continue; - } else if (readIf("LOCAL")) { - r = new TimeZoneOperation(r, null); - continue; - } else { - setTokenIndex(index); - } - } else if (readIf("FORMAT")) { - if (readIf("JSON")) { - r = new Format(r, FormatEnum.JSON); - continue; - } else { - setTokenIndex(index); - } + if (readIf("REGCLASS")) { + return new Regclass(r); } - break; } - return r; + return new CastSpecification(r, parseColumnWithType(null)); } private Expression readCurrentGeneralValueSpecification(int specification) { @@ -5763,6 +5782,19 @@ private boolean readIf(int tokenType) { return false; } + private boolean readIf(int tokenType1, int tokenType2) { + int size = tokens.size(); + if (tokenType1 == currentTokenType) { + int i = tokenIndex + 1; + if (i < size && tokens.get(i).tokenType() == tokenType2) { + setTokenIndex(i + 1); + return true; + } + } + addExpected(tokenType1, tokenType2); + return false; + } + private boolean isToken(String tokenName) { if (!token.isQuoted() && equalsToken(tokenName, currentToken)) { return true; @@ -5802,6 +5834,12 @@ private void addExpected(int tokenType) { } } + private void addExpected(int tokenType1, int tokenType2) { + if (expectedList != null) { + expectedList.add(TOKENS[tokenType1] + ' ' + TOKENS[tokenType2]); + } + } + private void addMultipleExpected(int ... tokenTypes) { for (int tokenType : tokenTypes) { expectedList.add(TOKENS[tokenType]); @@ -6919,9 +6957,7 @@ private TableValueConstructor parseValues() { } private ArrayList parseValuesRow(ArrayList row) { - if (readIf(ROW)) { - read(OPEN_PAREN); - } else if (!readIf(OPEN_PAREN)) { + if (!readIf(ROW, OPEN_PAREN) && !readIf(OPEN_PAREN)) { row.add(readExpression()); return row; } From 6a5fe627307da41397dd1c0223c7ba32cb3f3307 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 21:54:10 +0800 Subject: [PATCH 85/93] Improve NON_KEYWORDS and remove FILTER from context-sensitive keywords --- h2/src/docsrc/html/advanced.html | 2 - h2/src/main/org/h2/command/Parser.java | 455 ++++++++++-------------- h2/src/main/org/h2/util/ParserUtil.java | 1 - 3 files changed, 187 insertions(+), 271 deletions(-) diff --git a/h2/src/docsrc/html/advanced.html b/h2/src/docsrc/html/advanced.html index b4e1d3fc55..05b0b96581 100644 --- a/h2/src/docsrc/html/advanced.html +++ b/h2/src/docsrc/html/advanced.html @@ -529,8 +529,6 @@

                            Keywords / Reserved Words

          - - diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index f158b3dea2..3a84d0ea45 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -143,6 +143,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.StringJoiner; import java.util.TreeSet; import org.h2.api.ErrorCode; import org.h2.api.IntervalQualifier; @@ -969,8 +970,7 @@ private TransactionCommand parseRollback() { return command; } readIf("WORK"); - if (readIf(TO)) { - read("SAVEPOINT"); + if (readIf(TO, "SAVEPOINT")) { command = new TransactionCommand(session, CommandInterface.ROLLBACK_TO_SAVEPOINT); command.setSavepointName(readIdentifier()); } else { @@ -1380,7 +1380,7 @@ private Prepared parseShow() { } else if (readIf("DATABASES") || readIf("SCHEMAS")) { // for MySQL compatibility buff.append("SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA"); - } else if (database.getMode().getEnum() == ModeEnum.PostgreSQL && readIf("ALL")) { + } else if (database.getMode().getEnum() == ModeEnum.PostgreSQL && readIf(ALL)) { // for PostgreSQL compatibility buff.append("NAME, SETTING FROM PG_CATALOG.PG_SETTINGS"); } @@ -1554,8 +1554,7 @@ private Prepared parseMergeInto(TableFilter targetTableFilter, int start) { } command.setColumns(parseColumnList(table)); } - if (readIf(KEY)) { - read(OPEN_PAREN); + if (readIf(KEY, OPEN_PAREN)) { command.setKeys(parseColumnList(table)); } if (readIf(VALUES)) { @@ -1662,8 +1661,7 @@ private Insert parseInsert(int start) { } readValues: { if (!requireQuery) { - if (overridingSystem == null && readIf(DEFAULT)) { - read(VALUES); + if (overridingSystem == null && readIf(DEFAULT, VALUES)) { command.addRow(new Expression[0]); break readValues; } @@ -1687,14 +1685,10 @@ private Insert parseInsert(int start) { private Boolean readIfOverriding() { Boolean overridingSystem = null; - if (readIf("OVERRIDING")) { - if (readIf(USER)) { - overridingSystem = Boolean.FALSE; - } else { - read("SYSTEM"); - overridingSystem = Boolean.TRUE; - } - read(VALUE); + if (readIf("OVERRIDING", USER, VALUE)) { + overridingSystem = Boolean.FALSE; + } else if (readIf("OVERRIDING", "SYSTEM", VALUE)) { + overridingSystem = Boolean.TRUE; } return overridingSystem; } @@ -1716,10 +1710,7 @@ private void parseInsertSet(Insert command, Table table, Column[] columns) { private void parseInsertCompatibility(Insert command, Table table, Mode mode) { if (mode.onDuplicateKeyUpdate) { - if (readIf(ON)) { - read("DUPLICATE"); - read(KEY); - read("UPDATE"); + if (readIf(ON, "DUPLICATE", KEY, "UPDATE")) { do { String columnName = readIdentifier(); if (readIf(DOT)) { @@ -1745,10 +1736,7 @@ private void parseInsertCompatibility(Insert command, Table table, Mode mode) { } } if (mode.insertOnConflict) { - if (readIf(ON)) { - read("CONFLICT"); - read("DO"); - read("NOTHING"); + if (readIf(ON, "CONFLICT", "DO", "NOTHING")) { command.setIgnore(true); } } @@ -1823,9 +1811,8 @@ private TableFilter readTablePrimary() { TableValueConstructor query = parseValues(); alias = session.getNextSystemIdentifier(sqlCommand); table = query.toTable(alias, null, parameters, createView != null, currentSelect); - } else if (readIf(TABLE)) { + } else if (readIf(TABLE, OPEN_PAREN)) { // Table function derived table - read(OPEN_PAREN); ArrayTableFunction function = readTableFunction(ArrayTableFunction.TABLE); table = new FunctionTable(database.getMainSchema(), session, function); } else { @@ -2084,8 +2071,7 @@ private ArrayList readDerivedColumnNames() { } private void discardWithTableHints() { - if (readIf(WITH)) { - read(OPEN_PAREN); + if (readIf(WITH, OPEN_PAREN)) { do { discardTableHint(); } while (readIfMore()); @@ -2111,11 +2097,9 @@ private Prepared parseTruncate() { read(TABLE); Table table = readTableOrView(); boolean restart = database.getMode().truncateTableRestartIdentity; - if (readIf("CONTINUE")) { - read("IDENTITY"); + if (readIf("CONTINUE", "IDENTITY")) { restart = false; - } else if (readIf("RESTART")) { - read("IDENTITY"); + } else if (readIf("RESTART", "IDENTITY")) { restart = true; } TruncateTable command = new TruncateTable(session); @@ -2125,8 +2109,7 @@ private Prepared parseTruncate() { } private boolean readIfExists(boolean ifExists) { - if (readIf(IF)) { - read(EXISTS); + if (readIf(IF, EXISTS)) { ifExists = true; } return ifExists; @@ -2305,12 +2288,10 @@ private Prepared parseDrop() { command.setDropAction(dropAction); } return command; - } else if (readIf(ALL)) { - read("OBJECTS"); + } else if (readIf(ALL, "OBJECTS")) { DropDatabase command = new DropDatabase(session); command.setDropAllObjects(true); - if (readIf("DELETE")) { - read("FILES"); + if (readIf("DELETE", "FILES")) { command.setDeleteFiles(true); } return command; @@ -2430,8 +2411,7 @@ private Expression readJoinSpecification(TableFilter filter1, TableFilter filter Expression on = null; if (readIf(ON)) { on = readExpression(); - } else if (readIf(USING)) { - read(OPEN_PAREN); + } else if (readIf(USING, OPEN_PAREN)) { do { String columnName = readIdentifier(); on = addJoinColumn(on, filter1, filter2, filter1.getColumn(columnName, false), @@ -2671,8 +2651,7 @@ private Query parseQueryTerm() { } private void parseEndOfQuery(Query command) { - if (readIf(ORDER)) { - read("BY"); + if (readIf(ORDER, "BY")) { Select oldSelect = currentSelect; if (command instanceof Select) { currentSelect = (Select) command; @@ -2725,8 +2704,7 @@ private void parseEndOfQuery(Query command) { read("ROWS"); } } - if (readIf(WITH)) { - read("TIES"); + if (readIf(WITH, "TIES")) { command.setWithTies(true); } else { read("ONLY"); @@ -2773,9 +2751,7 @@ private void parseIsolationClause() { if (readIf(WITH)) { if (readIf("RR") || readIf("RS")) { // concurrent-access-resolution clause - if (readIf("USE")) { - read(AND); - read("KEEP"); + if (readIf("USE", AND, "KEEP")) { if (readIf("SHARE") || readIf("UPDATE") || readIf("EXCLUSIVE")) { // ignore @@ -2849,15 +2825,13 @@ private void parseSelectExpressions(Select command) { if (readIf("PERCENT")) { command.setFetchPercent(true); } - if (readIf(WITH)) { - read("TIES"); + if (readIf(WITH, "TIES")) { command.setWithTies(true); } currentSelect = temp; } if (readIf(DISTINCT)) { - if (readIf(ON)) { - read(OPEN_PAREN); + if (readIf(ON, OPEN_PAREN)) { ArrayList distinctExpressions = Utils.newSmallArrayList(); do { distinctExpressions.add(readExpression()); @@ -2921,8 +2895,7 @@ private Select parseSelect(int start) { // the group by is read for the outer select (or not a select) // so that columns that are not grouped can be used currentSelect = oldSelect; - if (readIf(GROUP)) { - read("BY"); + if (readIf(GROUP, "BY")) { command.setGroupQuery(); ArrayList list = Utils.newSmallArrayList(); do { @@ -3156,17 +3129,12 @@ private Expression readCondition() { return new UniquePredicate(query); } default: - int index = tokenIndex; - if (readIf("INTERSECTS")) { - if (readIf(OPEN_PAREN)) { - Expression r1 = readConcat(); - read(COMMA); - Expression r2 = readConcat(); - read(CLOSE_PAREN); - return new Comparison(Comparison.SPATIAL_INTERSECTS, r1, r2, false); - } else { - setTokenIndex(index); - } + if (readIf("INTERSECTS", OPEN_PAREN)) { + Expression r1 = readConcat(); + read(COMMA); + Expression r2 = readConcat(); + read(CLOSE_PAREN); + return new Comparison(Comparison.SPATIAL_INTERSECTS, r1, r2, false); } if (expectedList != null) { addMultipleExpected(NOT, EXISTS, UNIQUE); @@ -3337,12 +3305,10 @@ private IsJsonPredicate readJsonPredicate(Expression left, boolean not, boolean itemType = JSONItemType.VALUE; } boolean unique = false; - if (readIf(WITH)) { - read(UNIQUE); + if (readIf(WITH, UNIQUE)) { readIf("KEYS"); unique = true; - } else if (readIf("WITHOUT")) { - read(UNIQUE); + } else if (readIf("WITHOUT", UNIQUE)) { readIf("KEYS"); } return new IsJsonPredicate(left, not, whenOperand, unique, itemType); @@ -3357,8 +3323,7 @@ private Expression readLikePredicate(Expression left, LikeType likeType, boolean private Expression readComparison(Expression left, int compareType, boolean whenOperand) { int start = tokenIndex; - if (readIf(ALL)) { - read(OPEN_PAREN); + if (readIf(ALL, OPEN_PAREN)) { if (isQuery()) { Query query = parseQuery(); left = new ConditionInQuery(left, false, whenOperand, query, true, compareType); @@ -3367,21 +3332,27 @@ private Expression readComparison(Expression left, int compareType, boolean when setTokenIndex(start); left = new Comparison(compareType, left, readConcat(), whenOperand); } - } else if (readIf(ANY) || readIf(SOME)) { - read(OPEN_PAREN); - if (currentTokenType == PARAMETER && compareType == Comparison.EQUAL) { - Parameter p = readParameter(); - left = new ConditionInParameter(left, false, whenOperand, p); - read(CLOSE_PAREN); - } else if (isQuery()) { - Query query = parseQuery(); - left = new ConditionInQuery(left, false, whenOperand, query, false, compareType); - read(CLOSE_PAREN); - } else { - setTokenIndex(start); - left = new Comparison(compareType, left, readConcat(), whenOperand); - } + } else if (readIf(ANY, OPEN_PAREN)) { + left = readAnyComparison(left, compareType, whenOperand, start); + } else if (readIf(SOME, OPEN_PAREN)) { + left = readAnyComparison(left, compareType, whenOperand, start); + } else { + left = new Comparison(compareType, left, readConcat(), whenOperand); + } + return left; + } + + private Expression readAnyComparison(Expression left, int compareType, boolean whenOperand, int start) { + if (currentTokenType == PARAMETER && compareType == Comparison.EQUAL) { + Parameter p = readParameter(); + left = new ConditionInParameter(left, false, whenOperand, p); + read(CLOSE_PAREN); + } else if (isQuery()) { + Query query = parseQuery(); + left = new ConditionInQuery(left, false, whenOperand, query, false, compareType); + read(CLOSE_PAREN); } else { + setTokenIndex(start); left = new Comparison(compareType, left, readConcat(), whenOperand); } return left; @@ -3668,8 +3639,7 @@ private void readAggregateOrder(Aggregate r, Expression expr, boolean parseSortT } private ArrayList readIfOrderBy() { - if (readIf(ORDER)) { - read("BY"); + if (readIf(ORDER, "BY")) { return parseSortSpecificationList(); } return null; @@ -3726,9 +3696,7 @@ private boolean readDistinctAgg() { } private void readFilterAndOver(AbstractAggregate aggregate) { - if (readIf("FILTER")) { - read(OPEN_PAREN); - read(WHERE); + if (readIf("FILTER", OPEN_PAREN, WHERE)) { Expression filterCondition = readExpression(); read(CLOSE_PAREN); aggregate.setFilterCondition(filterCondition); @@ -3766,8 +3734,7 @@ private Window readWindowSpecification() { } } ArrayList partitionBy = null; - if (readIf("PARTITION")) { - read("BY"); + if (readIf("PARTITION", "BY")) { partitionBy = Utils.newSmallArrayList(); do { Expression expr = readExpression(); @@ -3803,8 +3770,7 @@ private WindowFrame readWindowFrame() { int sqlIndex = token.start(); WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS; if (readIf("EXCLUDE")) { - if (readIf("CURRENT")) { - read(ROW); + if (readIf("CURRENT", ROW)) { exclusion = WindowFrameExclusion.EXCLUDE_CURRENT_ROW; } else if (readIf(GROUP)) { exclusion = WindowFrameExclusion.EXCLUDE_GROUP; @@ -4531,8 +4497,7 @@ private ArrayTableFunction readUnnestFunction() { columns.add(new Column("C" + ++i, columnType)); } while (readIfMore()); } - if (readIf(WITH)) { - read("ORDINALITY"); + if (readIf(WITH, "ORDINALITY")) { columns.add(new Column("NORD", TypeInfo.TYPE_INTEGER)); } f.setColumns(columns); @@ -4739,61 +4704,38 @@ private WindowFunction readWindowFunction(String name) { } private void readFromFirstOrLast(WindowFunction function) { - if (readIf(FROM) && !readIf("FIRST")) { - read("LAST"); + if (readIf(FROM, "LAST")) { function.setFromLast(true); + } else { + readIf(FROM, "FIRST"); } } private void readRespectOrIgnoreNulls(WindowFunction function) { - if (readIf("RESPECT")) { - read("NULLS"); - } else if (readIf("IGNORE")) { - read("NULLS"); + if (readIf("IGNORE", "NULLS")) { function.setIgnoreNulls(true); + } else { + readIf("RESPECT", "NULLS"); } } private boolean readJsonObjectFunctionFlags(ExpressionWithFlags function, boolean forArray) { - int start = tokenIndex; boolean result = false; int flags = function.getFlags(); - if (readIf(NULL)) { - if (readIf(ON)) { - read(NULL); - flags &= ~JsonConstructorUtils.JSON_ABSENT_ON_NULL; - result = true; - } else { - setTokenIndex(start); - return false; - } - } else if (readIf("ABSENT")) { - if (readIf(ON)) { - read(NULL); - flags |= JsonConstructorUtils.JSON_ABSENT_ON_NULL; - result = true; - } else { - setTokenIndex(start); - return false; - } + if (readIf(NULL, ON, NULL)) { + flags &= ~JsonConstructorUtils.JSON_ABSENT_ON_NULL; + result = true; + } else if (readIf("ABSENT", ON, NULL)) { + flags |= JsonConstructorUtils.JSON_ABSENT_ON_NULL; + result = true; } if (!forArray) { - if (readIf(WITH)) { - read(UNIQUE); - read("KEYS"); + if (readIf(WITH, UNIQUE, "KEYS")) { flags |= JsonConstructorUtils.JSON_WITH_UNIQUE_KEYS; result = true; - } else if (readIf("WITHOUT")) { - if (readIf(UNIQUE)) { - read("KEYS"); - flags &= ~JsonConstructorUtils.JSON_WITH_UNIQUE_KEYS; - result = true; - } else if (result) { - throw getSyntaxError(); - } else { - setTokenIndex(start); - return false; - } + } else if (readIf("WITHOUT", UNIQUE, "KEYS")) { + flags &= ~JsonConstructorUtils.JSON_WITH_UNIQUE_KEYS; + result = true; } } if (result) { @@ -4853,8 +4795,7 @@ private Expression readIfWildcardRowidOrSequencePseudoColumn(String schema, Stri private Wildcard parseWildcard(String schema, String objectName) { Wildcard wildcard = new Wildcard(schema, objectName); - if (readIf(EXCEPT)) { - read(OPEN_PAREN); + if (readIf(EXCEPT, OPEN_PAREN)) { ArrayList exceptColumns = Utils.newSmallArrayList(); do { String s = null, t = null; @@ -5352,11 +5293,9 @@ private Expression readTermWithIdentifier(String name, boolean quoted) { switch (name.charAt(0) & 0xffdf) { case 'C': if (equalsToken("CURRENT", name)) { - int index = tokenIndex; - if (readIf(VALUE) && readIf(FOR)) { + if (readIf(VALUE, FOR)) { return new SequenceValue(readSequence()); } - setTokenIndex(index); if (database.getMode().getEnum() == ModeEnum.DB2) { return parseDB2SpecialRegisters(name); } @@ -5414,18 +5353,14 @@ && equalsToken("E", name)) { break; case 'N': if (equalsToken("NEXT", name)) { - int index = tokenIndex; - if (readIf(VALUE) && readIf(FOR)) { + if (readIf(VALUE, FOR)) { return new SequenceValue(readSequence(), getCurrentPreparedOrSelect()); } - setTokenIndex(index); } break; case 'T': if (equalsToken("TIME", name)) { - if (readIf(WITH)) { - read("TIME"); - read("ZONE"); + if (readIf(WITH, "TIME", "ZONE")) { if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) { throw getSyntaxError(); } @@ -5433,11 +5368,7 @@ && equalsToken("E", name)) { read(); return ValueExpression.get(ValueTimeTimeZone.parse(time)); } else { - boolean without = readIf("WITHOUT"); - if (without) { - read("TIME"); - read("ZONE"); - } + boolean without = readIf("WITHOUT", "TIME", "ZONE"); if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) { String time = token.value(session).getString(); read(); @@ -5447,9 +5378,7 @@ && equalsToken("E", name)) { } } } else if (equalsToken("TIMESTAMP", name)) { - if (readIf(WITH)) { - read("TIME"); - read("ZONE"); + if (readIf(WITH, "TIME", "ZONE")) { if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) { throw getSyntaxError(); } @@ -5457,11 +5386,7 @@ && equalsToken("E", name)) { read(); return ValueExpression.get(ValueTimestampTimeZone.parse(timestamp, session)); } else { - boolean without = readIf("WITHOUT"); - if (without) { - read("TIME"); - read("ZONE"); - } + boolean without = readIf("WITHOUT", "TIME", "ZONE"); if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) { String timestamp = token.value(session).getString(); read(); @@ -5526,9 +5451,7 @@ private Expression readInterval() { private Expression parseDB2SpecialRegisters(String name) { // Only "CURRENT" name is supported if (readIf("TIMESTAMP")) { - if (readIf(WITH)) { - read("TIME"); - read("ZONE"); + if (readIf(WITH, "TIME", "ZONE")) { return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIMESTAMP, readIf(OPEN_PAREN), null); } @@ -5795,6 +5718,39 @@ private boolean readIf(int tokenType1, int tokenType2) { return false; } + private boolean readIf(int tokenType1, String tokenName2) { + int size = tokens.size(); + if (tokenType1 == currentTokenType) { + int i = tokenIndex + 1; + if (i < size) { + Token token2 = tokens.get(i); + if (!token2.isQuoted() && equalsToken(tokenName2, token2.asIdentifier())) { + setTokenIndex(i + 1); + return true; + } + } + } + addExpected(TOKENS[tokenType1], tokenName2); + return false; + } + + private boolean readIf(Object... tokensTypesOrNames) { + int count = tokensTypesOrNames.length; + int size = tokens.size(); + int i = tokenIndex; + check: if (i + count < size) { + for (Object tokenTypeOrName : tokensTypesOrNames) { + if (!testToken(tokenTypeOrName, tokens.get(i++))) { + break check; + } + } + setTokenIndex(i); + return true; + } + addExpected(tokensTypesOrNames); + return false; + } + private boolean isToken(String tokenName) { if (!token.isQuoted() && equalsToken(tokenName, currentToken)) { return true; @@ -5803,6 +5759,11 @@ private boolean isToken(String tokenName) { return false; } + private boolean testToken(Object expected, Token token) { + return expected instanceof Integer ? (int) expected == token.tokenType() + : !token.isQuoted() && equalsToken((String) expected, token.asIdentifier()); + } + private boolean isToken(int tokenType) { if (tokenType == currentTokenType) { return true; @@ -5840,6 +5801,22 @@ private void addExpected(int tokenType1, int tokenType2) { } } + private void addExpected(String tokenType1, String tokenType2) { + if (expectedList != null) { + expectedList.add(tokenType1 + ' ' + tokenType2); + } + } + + private void addExpected(Object... tokens) { + if (expectedList != null) { + StringJoiner j = new StringJoiner(" "); + for (Object token : tokens) { + j.add(token instanceof Integer ? TOKENS[(int) token] : (String) token); + } + expectedList.add(j.toString()); + } + } + private void addMultipleExpected(int ... tokenTypes) { for (int tokenType : tokenTypes) { expectedList.add(TOKENS[tokenType]); @@ -5945,8 +5922,7 @@ private Column parseColumnForTable(String columnName, boolean defaultNullable) { if (readIf(AS)) { column.setGeneratedExpression(readExpression()); } else if (readIf(DEFAULT)) { - if (readIf(ON)) { - read(NULL); + if (readIf(ON, NULL)) { defaultOnNull = true; break defaultIdentityGeneration; } @@ -5972,8 +5948,7 @@ private Column parseColumnForTable(String columnName, boolean defaultNullable) { column.setGeneratedExpression(readExpression()); } } - if (!column.isGenerated() && readIf(ON)) { - read("UPDATE"); + if (!column.isGenerated() && readIf(ON, "UPDATE")) { column.setOnUpdateExpression(session, readExpression()); } nullConstraint = parseNotNullConstraint(nullConstraint); @@ -6001,9 +5976,7 @@ private Column parseColumnForTable(String columnName, boolean defaultNullable) { "Internal Error - unhandled case: " + nullConstraint.name()); } if (!defaultOnNull) { - if (readIf(DEFAULT)) { - read(ON); - read(NULL); + if (readIf(DEFAULT, ON, NULL)) { defaultOnNull = true; } else if (readIf("NULL_TO_DEFAULT")) { defaultOnNull = true; @@ -6302,9 +6275,7 @@ private TypeInfo readIfDataType1() { readIf("UNSIGNED"); } if (mode.forBitData && DataType.isStringType(t)) { - if (readIf(FOR)) { - read("BIT"); - read("DATA"); + if (readIf(FOR, "BIT", "DATA")) { dataType = DataType.getDataType(t = Value.VARBINARY); } } @@ -6395,13 +6366,10 @@ private TypeInfo parseTimeType() { read(CLOSE_PAREN); } int type = Value.TIME; - if (readIf(WITH)) { - read("TIME"); - read("ZONE"); + if (readIf(WITH, "TIME", "ZONE")) { type = Value.TIME_TZ; - } else if (readIf("WITHOUT")) { - read("TIME"); - read("ZONE"); + } else { + readIf("WITHOUT", "TIME", "ZONE"); } return TypeInfo.getTypeInfo(type, -1L, scale, null); } @@ -6421,13 +6389,10 @@ private TypeInfo parseTimestampType() { read(CLOSE_PAREN); } int type = Value.TIMESTAMP; - if (readIf(WITH)) { - read("TIME"); - read("ZONE"); + if (readIf(WITH, "TIME", "ZONE")) { type = Value.TIMESTAMP_TZ; - } else if (readIf("WITHOUT")) { - read("TIME"); - read("ZONE"); + } else { + readIf("WITHOUT", "TIME", "ZONE"); } return TypeInfo.getTypeInfo(type, -1L, scale, null); } @@ -6460,8 +6425,7 @@ private TypeInfo readIntervalQualifier() { precision = readNonNegativeInt(); read(CLOSE_PAREN); } - if (readIf(TO)) { - read(MONTH); + if (readIf(TO, MONTH)) { qualifier = IntervalQualifier.YEAR_TO_MONTH; } else { qualifier = IntervalQualifier.YEAR; @@ -6539,8 +6503,7 @@ private TypeInfo readIntervalQualifier() { precision = readNonNegativeInt(); read(CLOSE_PAREN); } - if (readIf(TO)) { - read(SECOND); + if (readIf(TO, SECOND)) { if (readIf(OPEN_PAREN)) { scale = readNonNegativeInt(); read(CLOSE_PAREN); @@ -6721,8 +6684,7 @@ private long readPrecision(int valueType) { private Prepared parseCreate() { boolean orReplace = false; - if (readIf(OR)) { - read("REPLACE"); + if (readIf(OR, "REPLACE")) { orReplace = true; } boolean force = readIf("FORCE"); @@ -6756,15 +6718,13 @@ private Prepared parseCreate() { } else if (readIf("CACHED")) { cached = true; } - if (readIf("LOCAL")) { - read("TEMPORARY"); + if (readIf("LOCAL", "TEMPORARY")) { if (readIf("LINKED")) { return parseCreateLinkedTable(true, false, force); } read(TABLE); return parseCreateTable(true, false, cached); - } else if (readIf("GLOBAL")) { - read("TEMPORARY"); + } else if (readIf("GLOBAL", "TEMPORARY")) { if (readIf("LINKED")) { return parseCreateLinkedTable(true, true, force); } @@ -6789,8 +6749,7 @@ private Prepared parseCreate() { String indexName = null; Schema oldSchema = null; boolean ifNotExists = false; - if (session.isQuirksMode() && readIf(PRIMARY)) { - read(KEY); + if (session.isQuirksMode() && readIf(PRIMARY, KEY)) { if (readIf("HASH")) { hash = true; } @@ -6970,20 +6929,12 @@ private ArrayList parseValuesRow(ArrayList row) { private Call parseCall() { Call command = new Call(session); currentPrepared = command; - int index = tokenIndex; - boolean canBeFunction; - switch (currentTokenType) { - case IDENTIFIER: - canBeFunction = true; - break; - case TABLE: - read(); - read(OPEN_PAREN); + if (readIf(TABLE, OPEN_PAREN)) { command.setTableFunction(readTableFunction(ArrayTableFunction.TABLE)); return command; - default: - canBeFunction = false; } + int index = tokenIndex; + boolean canBeFunction = isIdentifier(); try { command.setExpression(readExpression()); } catch (DbException e) { @@ -7060,9 +7011,7 @@ private CreateSequence parseCreateSequence() { } private boolean readIfNotExists() { - if (readIf(IF)) { - read(NOT); - read(EXISTS); + if (readIf(IF, NOT, EXISTS)) { return true; } return false; @@ -7119,8 +7068,7 @@ private CreateDomain parseCreateDomain() { if (readIf(DEFAULT)) { command.setDefaultExpression(readExpression()); } - if (readIf(ON)) { - read("UPDATE"); + if (readIf(ON, "UPDATE")) { command.setOnUpdateExpression(readExpression()); } // Compatibility with 1.4.200 and older versions @@ -7160,8 +7108,7 @@ private CreateTrigger parseCreateTrigger(boolean force) { String triggerName = readIdentifierWithSchema(null); Schema schema = getSchema(); boolean insteadOf, isBefore; - if (readIf("INSTEAD")) { - read("OF"); + if (readIf("INSTEAD", "OF")) { isBefore = true; insteadOf = true; } else if (readIf("BEFORE")) { @@ -7202,8 +7149,7 @@ private CreateTrigger parseCreateTrigger(boolean force) { command.setOnRollback(onRollback); command.setTypeMask(typeMask); command.setTableName(tableName); - if (readIf(FOR)) { - read("EACH"); + if (readIf(FOR, "EACH")) { if (readIf(ROW)) { command.setRowBased(true); } else { @@ -7645,8 +7591,7 @@ private DefineCommand parseAlterDomain() { command.setIfDomainExists(ifDomainExists); command.setExpression(null); return command; - } else if (readIf(ON)) { - read("UPDATE"); + } else if (readIf(ON, "UPDATE")) { AlterDomainExpressions command = new AlterDomainExpressions(session, schema, CommandInterface.ALTER_DOMAIN_ON_UPDATE); command.setDomainName(domainName); @@ -7683,8 +7628,7 @@ private DefineCommand parseAlterDomain() { command.setIfDomainExists(ifDomainExists); command.setExpression(readExpression()); return command; - } else if (readIf(ON)) { - read("UPDATE"); + } else if (readIf(ON, "UPDATE")) { AlterDomainExpressions command = new AlterDomainExpressions(session, schema, CommandInterface.ALTER_DOMAIN_ON_UPDATE); command.setDomainName(domainName); @@ -7704,8 +7648,7 @@ private DefineCommand parseAlterView() { if (!(tableView instanceof TableView) && !ifExists) { throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName); } - if (readIf("RENAME")) { - read(TO); + if (readIf("RENAME", TO)) { String newName = readIdentifierWithSchema(schema.getName()); checkSchema(schema); AlterTableRename command = new AlterTableRename(session, getSchema()); @@ -7767,8 +7710,7 @@ private boolean parseSequenceOptions(SequenceOptions options, CreateSequence com .getSQL(new StringBuilder("CREATE SEQUENCE AS "), HasSQL.TRACE_SQL_FLAGS).toString()); } options.setDataType(dataType); - } else if (readIf("START")) { - read(WITH); + } else if (readIf("START", WITH)) { options.setStartValue(readExpression()); } else if (readIf("RESTART")) { options.setRestartValue(readIf(WITH) ? readExpression() : ValueExpression.DEFAULT); @@ -7805,6 +7747,7 @@ private boolean parseCreateSequenceOption(CreateSequence command) { private boolean parseBasicSequenceOption(SequenceOptions options) { if (readIf("INCREMENT")) { + // TODO Why BY is optional? readIf("BY"); options.setIncrement(readExpression()); } else if (readIf("MINVALUE")) { @@ -7860,8 +7803,7 @@ private AlterUser parseAlterUser() { throw getSyntaxError(); } return command; - } else if (readIf("RENAME")) { - read(TO); + } else if (readIf("RENAME", TO)) { AlterUser command = new AlterUser(session); command.setType(CommandInterface.ALTER_USER_RENAME); command.setUser(database.getUser(userName)); @@ -8530,8 +8472,7 @@ private Prepared parseAlterTableAlter(Schema schema, String tableName, boolean i return command; } else if (readIf("DROP")) { if (readIf(DEFAULT)) { - if (readIf(ON)) { - read(NULL); + if (readIf(ON, NULL)) { AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); command.setTableName(tableName); command.setIfTableExists(ifTableExists); @@ -8549,8 +8490,7 @@ private Prepared parseAlterTableAlter(Schema schema, String tableName, boolean i return getAlterTableAlterColumnDropDefaultExpression(schema, tableName, ifTableExists, column, CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_IDENTITY); } - if (readIf(ON)) { - read("UPDATE"); + if (readIf(ON, "UPDATE")) { AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); command.setTableName(tableName); command.setIfTableExists(ifTableExists); @@ -8604,9 +8544,8 @@ private Prepared getAlterTableAlterColumnDropDefaultExpression(Schema schema, St private Prepared parseAlterTableAlterColumnIdentity(Schema schema, String tableName, boolean ifTableExists, Column column) { - int index = tokenIndex; Boolean always = null; - if (readIf(SET) && readIf("GENERATED")) { + if (readIf(SET, "GENERATED")) { if (readIf("ALWAYS")) { always = true; } else { @@ -8614,8 +8553,6 @@ private Prepared parseAlterTableAlterColumnIdentity(Schema schema, String tableN read(DEFAULT); always = false; } - } else { - setTokenIndex(index); } SequenceOptions options = new SequenceOptions(); if (!parseSequenceOptions(options, null, false, true) && always == null) { @@ -8644,8 +8581,7 @@ private Prepared parseAlterTableAlterColumnIdentity(Schema schema, String tableN private Prepared parseAlterTableAlterColumnSet(Schema schema, String tableName, boolean ifTableExists, boolean ifExists, String columnName, Column column) { - if (readIf("DATA")) { - read("TYPE"); + if (readIf("DATA", "TYPE")) { return parseAlterTableAlterColumnDataType(schema, tableName, columnName, ifTableExists, ifExists); } AlterTableAlterColumn command = new AlterTableAlterColumn( @@ -8663,8 +8599,7 @@ private Prepared parseAlterTableAlterColumnSet(Schema schema, String tableName, break; case NO_NULL_CONSTRAINT_FOUND: if (readIf(DEFAULT)) { - if (readIf(ON)) { - read(NULL); + if (readIf(ON, NULL)) { command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT_ON_NULL); command.setBooleanFlag(true); break; @@ -8672,8 +8607,7 @@ private Prepared parseAlterTableAlterColumnSet(Schema schema, String tableName, Expression defaultExpression = readExpression(); command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT); command.setDefaultExpression(defaultExpression); - } else if (readIf(ON)) { - read("UPDATE"); + } else if (readIf(ON, "UPDATE")) { Expression onUpdateExpression = readExpression(); command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_ON_UPDATE); command.setDefaultExpression(onUpdateExpression); @@ -8707,8 +8641,7 @@ private Prepared parseAlterTableDrop(Schema schema, String tableName, boolean if command.setDropAction(dropAction); } return command; - } else if (readIf(PRIMARY)) { - read(KEY); + } else if (readIf(PRIMARY, KEY)) { Table table = tableIfTableExists(schema, tableName, ifTableExists); if (table == null) { return new NoOperation(session); @@ -8754,8 +8687,7 @@ private Prepared parseAlterTableDrop(Schema schema, String tableName, boolean if } private Prepared parseAlterTableDropCompatibility(Schema schema, String tableName, boolean ifTableExists) { - if (readIf(FOREIGN)) { - read(KEY); + if (readIf(FOREIGN, KEY)) { // For MariaDB boolean ifExists = readIfExists(false); String constraintName = readIdentifierWithSchema(schema.getName()); @@ -9048,8 +8980,7 @@ private ConstraintActionType parseAction() { if (result != null) { return result; } - if (readIf("NO")) { - read("ACTION"); + if (readIf("NO", "ACTION")) { return ConstraintActionType.RESTRICT; } read(SET); @@ -9111,8 +9042,7 @@ private DefineCommand parseTableConstraintIf(String tableName, Schema schema, bo read(OPEN_PAREN); command = new AlterTableAddConstraint(session, schema, CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE, ifNotExists); - if (readIf(VALUE)) { - read(CLOSE_PAREN); + if (readIf(VALUE, CLOSE_PAREN)) { command.setIndexColumns(null); } else { command.setIndexColumns(parseIndexColumnList()); @@ -9121,16 +9051,16 @@ private DefineCommand parseTableConstraintIf(String tableName, Schema schema, bo String indexName = readIdentifierWithSchema(); command.setIndex(getSchema().findIndex(session, indexName)); } - if (compatibility && readIf(USING)) { - read("BTREE"); + if (compatibility) { + readIf(USING, "BTREE"); } break; case FOREIGN: read(); - command = new AlterTableAddConstraint(session, schema, - CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL, ifNotExists); read(KEY); read(OPEN_PAREN); + command = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL, ifNotExists); command.setIndexColumns(parseIndexColumnList()); if (readIf("INDEX")) { String indexName = readIdentifierWithSchema(); @@ -9221,11 +9151,7 @@ private void parseReferences(AlterTableAddConstraint command, command.setUpdateAction(parseAction()); } } - if (readIf(NOT)) { - if (!readIf("DEFERRABLE")) { - setTokenIndex(tokenIndex - 1); - } - } else { + if (!readIf(NOT, "DEFERRABLE")) { readIf("DEFERRABLE"); } } @@ -9258,8 +9184,7 @@ private CreateLinkedTable parseCreateLinkedTable(boolean temp, } command.setOriginalTable(originalTable); read(CLOSE_PAREN); - if (readIf("EMIT")) { - read("UPDATES"); + if (readIf("EMIT", "UPDATES")) { command.setEmitUpdates(true); } else if (readIf("READONLY")) { command.setReadOnly(true); @@ -9313,8 +9238,7 @@ private CreateTable parseCreateTable(boolean temp, boolean globalTemp, command.setTableEngineParams(readTableEngineParams()); } if (temp) { - if (readIf(ON)) { - read("COMMIT"); + if (readIf(ON, "COMMIT")) { if (readIf("DROP")) { command.setOnCommitDrop(); } else if (readIf("DELETE")) { @@ -9331,8 +9255,7 @@ private CreateTable parseCreateTable(boolean temp, boolean globalTemp, if (readIf("TRANSACTIONAL")) { command.setTransactional(true); } - } else if (!persistIndexes && readIf(NOT)) { - read("PERSISTENT"); + } else if (!persistIndexes && readIf(NOT, "PERSISTENT")) { command.setPersistData(false); } if (readIf("HIDDEN")) { @@ -9404,8 +9327,7 @@ private void readColumnConstraints(CommandWithColumns command, Schema schema, St } else { constraintName = null; } - if (!hasPrimaryKey && readIf(PRIMARY)) { - read(KEY); + if (!hasPrimaryKey && readIf(PRIMARY, KEY)) { hasPrimaryKey = true; boolean hash = readIf("HASH"); AlterTableAddConstraint pk = new AlterTableAddConstraint(session, schema, @@ -9495,9 +9417,7 @@ private void parseCreateTableMySQLTableOptions(CreateTable command) { throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, "AUTO_INCREMENT PRIMARY KEY"); } } else if (readIf(DEFAULT)) { - if (readIf("CHARACTER")) { - read(SET); - } else { + if (!readIf("CHARACTER", SET)) { readIf("CHARSET"); readIf("COLLATE"); } @@ -9548,8 +9468,7 @@ private NullConstraintType parseNotNullConstraint(NullConstraintType nullConstra private NullConstraintType parseNotNullConstraint() { NullConstraintType nullConstraint; - if (readIf(NOT)) { - read(NULL); + if (readIf(NOT, NULL)) { nullConstraint = NullConstraintType.NULL_IS_NOT_ALLOWED; } else if (readIf(NULL)) { nullConstraint = NullConstraintType.NULL_IS_ALLOWED; diff --git a/h2/src/main/org/h2/util/ParserUtil.java b/h2/src/main/org/h2/util/ParserUtil.java index 95498a4a26..0083887d9e 100644 --- a/h2/src/main/org/h2/util/ParserUtil.java +++ b/h2/src/main/org/h2/util/ParserUtil.java @@ -573,7 +573,6 @@ public class ParserUtil { 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); From 936a385f068a393f31d893ff25cbc27d3a98a94d Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sun, 5 Jun 2022 22:44:08 +0800 Subject: [PATCH 86/93] Fix password in test --- h2/src/test/org/h2/test/db/TestTableEngines.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/test/org/h2/test/db/TestTableEngines.java b/h2/src/test/org/h2/test/db/TestTableEngines.java index 3afd8cb47c..e81d72d7f3 100644 --- a/h2/src/test/org/h2/test/db/TestTableEngines.java +++ b/h2/src/test/org/h2/test/db/TestTableEngines.java @@ -76,7 +76,7 @@ private void testAdminPrivileges() throws SQLException { Statement stat = conn.createStatement(); stat.execute("CREATE USER U PASSWORD '1'"); stat.execute("GRANT ALTER ANY SCHEMA TO U"); - Connection connUser = getConnection("tableEngine", "U", "1"); + Connection connUser = getConnection("tableEngine", "U", getPassword("1")); Statement statUser = connUser.createStatement(); assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, statUser) .execute("CREATE TABLE T(ID INT, NAME VARCHAR) ENGINE \"" + EndlessTableEngine.class.getName() + '"'); From 05d5999c0a065b9974c1698cfb494a69fd06a4ef Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Tue, 7 Jun 2022 20:02:44 +0800 Subject: [PATCH 87/93] Add more helper methods --- h2/src/main/org/h2/command/Parser.java | 53 ++++++++++++++++++-------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 3a84d0ea45..8e77bab415 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -1822,7 +1822,7 @@ private TableFilter readTablePrimary() { schemaName = null; if (readIf(DOT)) { tableName = readIdentifierWithSchema2(tableName); - } else if (!quoted && readIf(TABLE)) { + } else if (!quoted && readIf(TABLE, OPEN_PAREN)) { table = readDataChangeDeltaTable(upperName(tableName), backupIndex); break label; } @@ -1949,7 +1949,6 @@ private TableFilter buildTableFilter(Table table, String alias, ArrayList Date: Sat, 11 Jun 2022 14:25:00 +0800 Subject: [PATCH 88/93] Fix missing parameters in some subqueries --- h2/src/docsrc/html/changelog.html | 2 + .../main/org/h2/command/CommandContainer.java | 17 +-- h2/src/main/org/h2/command/Parser.java | 133 ++++++++++-------- h2/src/main/org/h2/command/Prepared.java | 22 +++ h2/src/main/org/h2/command/Tokenizer.java | 14 +- .../h2/test/jdbc/TestPreparedStatement.java | 25 ++++ 6 files changed, 137 insertions(+), 76 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index 9a6628ea81..efa8af250f 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 #3534: Subquery has incorrect empty parameters since 2.1.210 that breaks sameResultAsLast() +
          • Issue #3390: "ROW" cannot be set as a non keyword in 2.x
          • Issue #3448: With linked table to postgreSQL, case-sensitive column names not respected in where part diff --git a/h2/src/main/org/h2/command/CommandContainer.java b/h2/src/main/org/h2/command/CommandContainer.java index 30fcf5bc53..5ab13a6c49 100644 --- a/h2/src/main/org/h2/command/CommandContainer.java +++ b/h2/src/main/org/h2/command/CommandContainer.java @@ -118,7 +118,11 @@ public CommandContainer(SessionLocal session, String sql, Prepared prepared) { @Override public ArrayList getParameters() { - return prepared.getParameters(); + ArrayList parameters = prepared.getParameters(); + if (parameters.size() > 0 && prepared.isWithParamValues()) { + parameters = new ArrayList<>(); + } + return parameters; } @Override @@ -137,20 +141,11 @@ private void recompileIfRequired() { prepared.setModificationMetaId(0); String sql = prepared.getSQL(); ArrayList tokens = prepared.getSQLTokens(); - ArrayList oldParams = prepared.getParameters(); Parser parser = new Parser(session); + parser.setSuppliedParameters(prepared.getParameters()); prepared = parser.parse(sql, tokens); long mod = prepared.getModificationMetaId(); prepared.setModificationMetaId(0); - ArrayList newParams = prepared.getParameters(); - for (int i = 0, size = Math.min(newParams.size(), oldParams.size()); i < size; i++) { - Parameter old = oldParams.get(i); - if (old.isValueSet()) { - Value v = old.getValue(session); - Parameter p = newParams.get(i); - p.setValue(v); - } - } prepared.prepare(); prepared.setModificationMetaId(mod); } diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 8e77bab415..98c7516475 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -449,7 +449,7 @@ public class Parser { private Select currentSelect; private List cteCleanups; private ArrayList parameters; - private ArrayList suppliedParameters; + private BitSet usedParameters = new BitSet(); private String schemaName; private ArrayList expectedList; private boolean rightsChecked; @@ -604,7 +604,6 @@ private CommandList prepareCommandList(CommandContainer command, Prepared p, Str // Next commands may depend on results of this command. return new CommandList(session, sql, command, list, parameters, remainingSql); } - suppliedParameters = parameters; try { p = parse(remainingSql, remainingTokens); } catch (DbException ex) { @@ -669,8 +668,6 @@ Prepared parse(String sql, ArrayList tokens) { throw e.addSQL(sql); } } - p.setPrepareAlways(recompileAlways); - p.setParameterList(parameters); return p; } @@ -680,12 +677,12 @@ private Prepared parse(boolean withExpectedList) { } else { expectedList = null; } - parameters = suppliedParameters != null ? suppliedParameters : Utils.newSmallArrayList(); currentSelect = null; currentPrepared = null; createView = null; cteCleanups = null; recompileAlways = false; + usedParameters.clear(); read(); Prepared p; try { @@ -697,6 +694,8 @@ private Prepared parse(boolean withExpectedList) { } throw t; } + p.setPrepareAlways(recompileAlways); + p.setParameterList(parameters); return p; } @@ -866,13 +865,6 @@ private Prepared parsePrepared() { if (c == null) { throw getSyntaxError(); } - if (parameters != null) { - for (int i = 0, size = parameters.size(); i < size; i++) { - if (parameters.get(i) == null) { - parameters.set(i, new Parameter(i)); - } - } - } boolean withParamValues = readIf(OPEN_BRACE); if (withParamValues) { do { @@ -893,7 +885,7 @@ private Prepared parsePrepared() { for (Parameter p : parameters) { p.checkSet(); } - parameters.clear(); + c.setWithParamValues(true); } if (withParamValues || c.getSQL() == null) { setSQL(c, start); @@ -1808,9 +1800,11 @@ private TableFilter readTablePrimary() { return readCorrelation(tableFilter); } } else if (readIf(VALUES)) { + BitSet outerUsedParameters = initParametersScope(); TableValueConstructor query = parseValues(); alias = session.getNextSystemIdentifier(sqlCommand); - table = query.toTable(alias, null, parameters, createView != null, currentSelect); + table = query.toTable(alias, null, getUsedParameters(outerUsedParameters), createView != null, + currentSelect); } else if (readIf(TABLE, OPEN_PAREN)) { // Table function derived table ArrayTableFunction function = readTableFunction(ArrayTableFunction.TABLE); @@ -1898,7 +1892,9 @@ private TableFilter readCorrelation(TableFilter tableFilter) { } private TableFilter readDerivedTableWithCorrelation() { + BitSet outerUsedParameters = initParametersScope(); Query query = parseQueryExpression(); + ArrayList queryParameters = getUsedParameters(outerUsedParameters); read(CLOSE_PAREN); Table table; String alias; @@ -1906,7 +1902,7 @@ private TableFilter readDerivedTableWithCorrelation() { IndexHints indexHints = null; if (readIfUseIndex()) { alias = session.getNextSystemIdentifier(sqlCommand); - table = query.toTable(alias, null, parameters, createView != null, currentSelect); + table = query.toTable(alias, null, queryParameters, createView != null, currentSelect); indexHints = parseIndexHints(table); } else { alias = readFromAlias(null); @@ -1919,13 +1915,13 @@ private TableFilter readDerivedTableWithCorrelation() { derivedColumnNames.toArray(new String[0]), query, new String[1]) .toArray(new Column[0]); } - table = query.toTable(alias, columnTemplates, parameters, createView != null, currentSelect); + table = query.toTable(alias, columnTemplates, queryParameters, createView != null, currentSelect); if (readIfUseIndex()) { indexHints = parseIndexHints(table); } } else { alias = session.getNextSystemIdentifier(sqlCommand); - table = query.toTable(alias, null, parameters, createView != null, currentSelect); + table = query.toTable(alias, null, queryParameters, createView != null, currentSelect); } } return buildTableFilter(table, alias, derivedColumnNames, indexHints); @@ -2567,26 +2563,18 @@ private Explain parseExplain() { } private Query parseQuery() { - int paramIndex = parameters.size(); + BitSet outerUsedParameters = initParametersScope(); Query command = parseQueryExpression(); - int size = parameters.size(); - ArrayList params = new ArrayList<>(size); - for (int i = paramIndex; i < size; i++) { - params.add(parameters.get(i)); - } + ArrayList params = getUsedParameters(outerUsedParameters); command.setParameterList(params); command.init(); return command; } private Prepared parseWithStatementOrQuery(int start) { - int paramIndex = parameters.size(); + BitSet outerUsedParameters = initParametersScope(); Prepared command = parseWith(); - int size = parameters.size(); - ArrayList params = new ArrayList<>(size); - for (int i = paramIndex; i < size; i++) { - params.add(parameters.get(i)); - } + ArrayList params = getUsedParameters(outerUsedParameters); command.setParameterList(params); if (command instanceof Query) { Query query = (Query) command; @@ -2877,6 +2865,7 @@ private Select parseSelect(int start) { Select command = new Select(session, currentSelect); Select oldSelect = currentSelect; Prepared oldPrepared = currentPrepared; + BitSet outerUsedParameters = initParametersScope(); currentSelect = command; currentPrepared = command; parseSelectExpressions(command); @@ -2949,7 +2938,7 @@ private Select parseSelect(int start) { command.setWindowQuery(); command.setQualify(readExpressionWithGlobalConditions()); } - command.setParameterList(parameters); + command.setParameterList(getUsedParameters(outerUsedParameters)); currentSelect = oldSelect; currentPrepared = oldPrepared; setSQL(command, start); @@ -4882,28 +4871,30 @@ private void checkDatabaseName(String databaseName) { } private Parameter readParameter() { - int index = ((Token.ParameterToken) token).index(); + int index = ((Token.ParameterToken) token).index() - 1; read(); - Parameter p; - if (parameters == null) { - parameters = Utils.newSmallArrayList(); - } - if (index > Constants.MAX_PARAMETER_INDEX) { - throw DbException.getInvalidValueException("parameter index", index); - } - index--; - if (parameters.size() <= index) { - parameters.ensureCapacity(index + 1); - while (parameters.size() < index) { - parameters.add(null); + usedParameters.set(index); + return parameters.get(index); + } + + private BitSet initParametersScope() { + BitSet outerUsedParameters = usedParameters; + usedParameters = new BitSet(); + return outerUsedParameters; + } + + private ArrayList getUsedParameters(BitSet outerUsedParameters) { + BitSet innerUsedParameters = usedParameters; + int size = innerUsedParameters.cardinality(); + ArrayList params = new ArrayList<>(size); + if (size > 0) { + for (int i = -1; (i = innerUsedParameters.nextSetBit(i + 1)) >= 0;) { + params.add(parameters.get(i)); } - p = new Parameter(index); - parameters.add(p); - } else if ((p = parameters.get(index)) == null) { - p = new Parameter(index); - parameters.set(index, p); } - return p; + outerUsedParameters.or(innerUsedParameters); + usedParameters = outerUsedParameters; + return params; } private Expression readTerm() { @@ -5880,8 +5871,32 @@ private void initialize(String sql, ArrayList tokens, boolean stopOnClose sql = ""; } sqlCommand = sql; - this.tokens = tokens == null ? new Tokenizer(database, identifiersToUpper, identifiersToLower, nonKeywords) - .tokenize(sql, stopOnCloseParen) : tokens; + if (tokens == null) { + BitSet usedParameters = new BitSet(); + this.tokens = new Tokenizer(database, identifiersToUpper, identifiersToLower, nonKeywords) + .tokenize(sql, stopOnCloseParen, usedParameters); + if (parameters == null) { + int l = usedParameters.length(); + if (l > Constants.MAX_PARAMETER_INDEX) { + throw DbException.getInvalidValueException("parameter index", l); + } + if (l > 0) { + parameters = new ArrayList<>(l); + for (int i = 0; i < l; i++) { + /* + * We need to create parameters even when they aren't + * actually used, for example, VALUES ?1, ?3 needs + * parameters ?1, ?2, and ?3. + */ + parameters.add(new Parameter(i)); + } + } else { + parameters = new ArrayList<>(); + } + } + } else { + this.tokens = tokens; + } resetTokenIndex(); } @@ -7398,6 +7413,8 @@ private TableView parseSingleCommonTableExpression(boolean isTemporary) { isTemporary, session, cteViewName, schema, columns, database); List columnTemplateList; String[] querySQLOutput = new String[1]; + BitSet outerUsedParameters = initParametersScope(); + ArrayList queryParameters; try { read(AS); read(OPEN_PAREN); @@ -7409,17 +7426,18 @@ private TableView parseSingleCommonTableExpression(boolean isTemporary) { columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery, querySQLOutput); } finally { + queryParameters = getUsedParameters(outerUsedParameters); TableView.destroyShadowTableForRecursiveExpression(isTemporary, session, recursiveTable); } return createCTEView(cteViewName, - querySQLOutput[0], columnTemplateList, + querySQLOutput[0], queryParameters, columnTemplateList, true/* allowRecursiveQueryDetection */, true/* add to session */, isTemporary); } - private TableView createCTEView(String cteViewName, String querySQL, + private TableView createCTEView(String cteViewName, String querySQL, ArrayList queryParameters, List columnTemplateList, boolean allowRecursiveQueryDetection, boolean addViewToSession, boolean isTemporary) { Schema schema = getSchemaWithDefault(); @@ -7432,7 +7450,7 @@ private TableView createCTEView(String cteViewName, String querySQL, TableView view; synchronized (session) { view = new TableView(schema, id, cteViewName, querySQL, - parameters, columnTemplateArray, session, + queryParameters, columnTemplateArray, session, allowRecursiveQueryDetection, false, true, isTemporary); if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) { @@ -7443,7 +7461,7 @@ private TableView createCTEView(String cteViewName, String querySQL, } else { session.removeLocalTempTable(view); } - view = new TableView(schema, id, cteViewName, querySQL, parameters, + view = new TableView(schema, id, cteViewName, querySQL, queryParameters, columnTemplateArray, session, false/* assume recursive */, false, true, isTemporary); @@ -9583,7 +9601,7 @@ public void setRightsChecked(boolean rightsChecked) { } public void setSuppliedParameters(ArrayList suppliedParameters) { - this.suppliedParameters = suppliedParameters; + this.parameters = suppliedParameters; } /** @@ -9593,7 +9611,6 @@ public void setSuppliedParameters(ArrayList suppliedParameters) { * @return the expression object */ public Expression parseExpression(String sql) { - parameters = Utils.newSmallArrayList(); initialize(sql, null, false); read(); return readExpression(); @@ -9606,7 +9623,6 @@ public Expression parseExpression(String sql) { * @return the expression object */ public Expression parseDomainConstraintExpression(String sql) { - parameters = Utils.newSmallArrayList(); initialize(sql, null, false); read(); try { @@ -9624,7 +9640,6 @@ public Expression parseDomainConstraintExpression(String sql) { * @return the table object */ public Table parseTableName(String sql) { - parameters = Utils.newSmallArrayList(); initialize(sql, null, false); read(); return readTableOrView(); diff --git a/h2/src/main/org/h2/command/Prepared.java b/h2/src/main/org/h2/command/Prepared.java index f9a88835d9..3bc96f53c3 100644 --- a/h2/src/main/org/h2/command/Prepared.java +++ b/h2/src/main/org/h2/command/Prepared.java @@ -51,6 +51,8 @@ public abstract class Prepared { */ protected ArrayList parameters; + private boolean withParamValues; + /** * If the query should be prepared before each execution. This is set for * queries with LIKE ?, because the query plan depends on the parameter @@ -170,6 +172,25 @@ public ArrayList getParameters() { return parameters; } + /** + * Returns whether values of parameters were specified in SQL. + * + * @return are values of parameters were specified in SQL + */ + public boolean isWithParamValues() { + return withParamValues; + } + + /** + * Sets whether values of parameters were specified in SQL. + * + * @param withParamValues + * are values of parameters were specified in SQL + */ + public void setWithParamValues(boolean withParamValues) { + this.withParamValues = withParamValues; + } + /** * Check if all parameters have been set. * @@ -470,4 +491,5 @@ public final SessionLocal getSession() { * @param dependencies collection of dependencies to populate */ public void collectDependencies(HashSet dependencies) {} + } diff --git a/h2/src/main/org/h2/command/Tokenizer.java b/h2/src/main/org/h2/command/Tokenizer.java index f0c413e546..fd6ce56474 100644 --- a/h2/src/main/org/h2/command/Tokenizer.java +++ b/h2/src/main/org/h2/command/Tokenizer.java @@ -159,7 +159,7 @@ public final class Tokenizer { this.nonKeywords = nonKeywords; } - ArrayList tokenize(String sql, boolean stopOnCloseParen) { + ArrayList tokenize(String sql, boolean stopOnCloseParen, BitSet parameters) { ArrayList tokens = new ArrayList<>(); int end = sql.length() - 1; boolean foundUnicode = false; @@ -205,7 +205,7 @@ ArrayList tokenize(String sql, boolean stopOnCloseParen) { i = stringEnd + 1; } else { i = parseParameterIndex(sql, end, i, tokens); - lastParameter = assignParameterIndex(tokens, lastParameter); + lastParameter = assignParameterIndex(tokens, lastParameter, parameters); continue loop; } } else { @@ -356,7 +356,7 @@ ArrayList tokenize(String sql, boolean stopOnCloseParen) { } } i = parseParameterIndex(sql, end, i, tokens); - lastParameter = assignParameterIndex(tokens, lastParameter); + lastParameter = assignParameterIndex(tokens, lastParameter, parameters); continue loop; } case '@': @@ -1343,18 +1343,20 @@ private static int parseParameterIndex(String sql, int end, int i, ArrayList tokens, int lastParameter) { + private static int assignParameterIndex(ArrayList tokens, int lastParameter, BitSet parameters) { Token.ParameterToken parameter = (Token.ParameterToken) tokens.get(tokens.size() - 1); - if (parameter.index == 0) { + int index = parameter.index; + if (index == 0) { if (lastParameter < 0) { throw DbException.get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS); } - parameter.index = ++lastParameter; + parameter.index = index = ++lastParameter; } else if (lastParameter > 0) { throw DbException.get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS); } else { lastParameter = -1; } + parameters.set(index - 1); return lastParameter; } diff --git a/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java b/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java index 7bbe4026b3..8e0493e419 100644 --- a/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java +++ b/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java @@ -117,6 +117,7 @@ public void test() throws Exception { testColumnMetaDataWithEquals(conn); testColumnMetaDataWithIn(conn); testMultipleStatements(conn); + testParameterInSubquery(conn); testAfterRollback(conn); conn.close(); testPreparedStatementWithLiteralsNone(); @@ -1724,6 +1725,30 @@ private void testMultipleStatements(Connection conn) throws SQLException { stmt.execute("DROP TABLE A, B"); } + private void testParameterInSubquery(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1(ID1 BIGINT PRIMARY KEY, S INT NOT NULL)"); + stat.execute("CREATE TABLE T2(ID1 BIGINT REFERENCES T1, ID2 BIGINT)"); + + stat.executeUpdate("INSERT INTO T1(ID1, S) VALUES(1, 1), (2, 1)"); + stat.executeUpdate("INSERT INTO T2(ID1, ID2) VALUES(1, 1), (2, 2)"); + + PreparedStatement query = conn.prepareStatement("SELECT ID2 FROM " + + "(SELECT * FROM T1 WHERE ID1 IN (SELECT ID1 FROM T2 WHERE ID2 = ?) AND S = ?) T1 " + + "JOIN T2 USING(ID1)"); + + query.setLong(1, 2L); + query.setInt(2, 1); + ResultSet rs = query.executeQuery(); + rs.next(); + assertEquals(2L, rs.getLong(1)); + query.setLong(1, 1L); + rs = query.executeQuery(); + rs.next(); + assertEquals(1L, rs.getLong(1)); + stat.execute("DROP TABLE T2, T1"); + } + private void testAfterRollback(Connection conn) throws SQLException { try (Statement stat = conn.createStatement()) { try { From c0d989e43f73cb90d01948009705301423712f5d Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 11 Jun 2022 19:24:24 +0800 Subject: [PATCH 89/93] Use DECFLOAT for NUMERIC without parameters in PostgreSQL compatibility mode --- h2/src/docsrc/html/changelog.html | 2 ++ h2/src/docsrc/html/features.html | 1 + h2/src/main/org/h2/command/Parser.java | 5 +++++ h2/src/main/org/h2/engine/Mode.java | 17 +++++++++++----- .../org/h2/test/scripts/datatypes/numeric.sql | 20 +++++++++++++++++++ 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index efa8af250f..b229e17ac9 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 #3538: In Postgres compatibility mode the NUMERIC type w/o scale should not default to 0 +
            • Issue #3534: Subquery has incorrect empty parameters since 2.1.210 that breaks sameResultAsLast()
            • Issue #3390: "ROW" cannot be set as a non keyword in 2.x diff --git a/h2/src/docsrc/html/features.html b/h2/src/docsrc/html/features.html index c01cc02bcb..c3f387dfdd 100644 --- a/h2/src/docsrc/html/features.html +++ b/h2/src/docsrc/html/features.html @@ -1089,6 +1089,7 @@

              PostgreSQL Compatibility Mode

            • ON CONFLICT DO NOTHING is supported in INSERT statements.
            • Spaces are trimmed from the right side of CHAR values, but CHAR values in result sets are right-padded with spaces to the declared length. +
            • NUMERIC and DECIMAL/DEC data types without parameters are treated like DECFLOAT data type.
            • MONEY data type is treated like NUMERIC(19, 2) data type.
            • Datetime value functions return the same value within a transaction.
            • ARRAY_SLICE() out of bounds parameters are silently corrected. diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java index 98c7516475..36c0cfbb1b 100644 --- a/h2/src/main/org/h2/command/Parser.java +++ b/h2/src/main/org/h2/command/Parser.java @@ -6164,6 +6164,9 @@ private TypeInfo readIfDataType1() { original = "CHARACTER LARGE OBJECT"; } break; + case "DATE": + return database.getMode().dateIsTimestamp0 ? TypeInfo.getTypeInfo(Value.TIMESTAMP, -1L, 0, null) + : TypeInfo.TYPE_DATE; case "DATETIME": case "DATETIME2": return parseDateTimeType(false); @@ -6370,6 +6373,8 @@ private TypeInfo parseNumericType(boolean decimal) { } } read(CLOSE_PAREN); + } else if (database.getMode().numericIsDecfloat) { + return TypeInfo.TYPE_DECFLOAT; } return TypeInfo.getTypeInfo(Value.NUMERIC, precision, scale, decimal ? ExtTypeInfoNumeric.DECIMAL : null); } diff --git a/h2/src/main/org/h2/engine/Mode.java b/h2/src/main/org/h2/engine/Mode.java index 3ae902656c..f836a7728a 100644 --- a/h2/src/main/org/h2/engine/Mode.java +++ b/h2/src/main/org/h2/engine/Mode.java @@ -423,6 +423,16 @@ public enum CharPadding { */ public boolean autoIncrementClause; + /** + * Whether DATE data type is parsed as TIMESTAMP(0). + */ + public boolean dateIsTimestamp0; + + /** + * Whether NUMERIC and DECIMAL/DEC without parameters are parsed as DECFLOAT. + */ + public boolean numericIsDecfloat; + /** * An optional Set of hidden/disallowed column types. * Certain DBMSs don't support all column types provided by H2, such as @@ -646,13 +656,9 @@ public enum CharPadding { mode.minusIsExcept = true; mode.expressionNames = ExpressionNames.ORIGINAL_SQL; mode.viewExpressionNames = ViewExpressionNames.EXCEPTION; + mode.dateIsTimestamp0 = true; mode.typeByNameMap.put("BINARY_FLOAT", DataType.getDataType(Value.REAL)); mode.typeByNameMap.put("BINARY_DOUBLE", DataType.getDataType(Value.DOUBLE)); - dt = DataType.createDate(/* 2001-01-01 23:59:59 */ 19, 19, "DATE", false, 0, 0); - dt.type = Value.TIMESTAMP; - dt.sqlType = Types.TIMESTAMP; - dt.specialPrecisionScale = true; - mode.typeByNameMap.put("DATE", dt); add(mode); mode = new Mode(ModeEnum.PostgreSQL); @@ -673,6 +679,7 @@ public enum CharPadding { mode.allowUsingFromClauseInUpdateStatement = true; mode.limit = true; mode.serialDataTypes = true; + mode.numericIsDecfloat = true; // Enumerate all H2 types NOT supported by PostgreSQL: Set disallowedTypes = new java.util.HashSet<>(); disallowedTypes.add("NUMBER"); diff --git a/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql b/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql index 43536cefb0..efa4bec939 100644 --- a/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql +++ b/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql @@ -184,5 +184,25 @@ INSERT INTO TEST VALUES CAST(20 AS NUMERIC(2)); DROP TABLE TEST; > ok +SET MODE PostgreSQL; +> ok + +CREATE TABLE TEST(A NUMERIC, B DECIMAL, C DEC, D NUMERIC(1)); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, + DECLARED_DATA_TYPE, DECLARED_NUMERIC_PRECISION, DECLARED_NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DECLARED_DATA_TYPE DECLARED_NUMERIC_PRECISION DECLARED_NUMERIC_SCALE +> ----------- --------- ----------------- ----------------------- ------------- ------------------ -------------------------- ---------------------- +> A DECFLOAT 100000 10 null DECFLOAT null null +> B DECFLOAT 100000 10 null DECFLOAT null null +> C DECFLOAT 100000 10 null DECFLOAT null null +> D NUMERIC 1 10 0 NUMERIC 1 null +> rows (ordered): 4 + +DROP TABLE TEST; +> ok + SET MODE Regular; > ok From 1fbdd510301a0f4b86d9079a7b2d006481302689 Mon Sep 17 00:00:00 2001 From: Evgenij Ryazanov Date: Sat, 11 Jun 2022 19:29:57 +0800 Subject: [PATCH 90/93] Update PgJDBC to version 42.4.0 --- h2/pom.xml | 2 +- h2/src/tools/org/h2/build/Build.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/h2/pom.xml b/h2/pom.xml index 1bc18cf054..9476dbf837 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -44,7 +44,7 @@ 5.6.2 8.5.2 5.0.0 - 42.3.2 + 42.4.0 4.0.1 5.0.0 1.7.30 diff --git a/h2/src/tools/org/h2/build/Build.java b/h2/src/tools/org/h2/build/Build.java index c66b83d160..f8e7c38eb4 100644 --- a/h2/src/tools/org/h2/build/Build.java +++ b/h2/src/tools/org/h2/build/Build.java @@ -53,9 +53,9 @@ public class Build extends BuildBase { private static final String OSGI_VERSION = "5.0.0"; - private static final String PGJDBC_VERSION = "42.3.2"; + private static final String PGJDBC_VERSION = "42.4.0"; - private static final String PGJDBC_HASH = "8fd7a20f008a58b97b26ba5c5084ee61602203aa"; + private static final String PGJDBC_HASH = "21ff952426bbfe4a041c175407333d4a07c70931"; private static final String JAVAX_SERVLET_VERSION = "4.0.1"; From 01ad7fbdf8454de4023233b736785b59a326f1a6 Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 13 Jun 2022 09:38:11 -0400 Subject: [PATCH 91/93] release preparation --- README.md | 2 +- h2/src/docsrc/html/changelog.html | 769 +---------------------- h2/src/docsrc/html/download-archive.html | 4 + h2/src/docsrc/html/download.html | 8 +- h2/src/main/org/h2/engine/Constants.java | 6 +- h2/src/main/org/h2/mvstore/db/Store.java | 1 + h2/src/test/org/h2/samples/newsfeed.sql | 4 +- 7 files changed, 21 insertions(+), 773 deletions(-) diff --git a/README.md b/README.md index 4f2f2fa58c..715833bdb5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ More information: https://h2database.com com.h2database h2 - 2.1.212 + 2.1.214 ``` diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html index b229e17ac9..b0f75e9a0c 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -20,6 +20,11 @@

              Change Log

              Next Version (unreleased)

              +
                +
              • Nothing yet
              • +
              + +

              Version 2.1.214 (2022-06-13)

              • Issue #3538: In Postgres compatibility mode the NUMERIC type w/o scale should not default to 0
              • @@ -49,7 +54,7 @@

                Next Version (unreleased)

              -

              Next Version 2.1.212 (2022-04-09)

              +

              Version 2.1.212 (2022-04-09)

              • Issue #3512: BITNOT(BIT_NAND_AGG(...) OVER ()) produces wrong result
              • @@ -225,767 +230,5 @@

                Version 2.0.204 (2021-12-21)

              -

              Version 2.0.202 (2021-11-25)

              -
                -
              • Issue #3206: CVE Vulnerability CVE-2018-14335 -
              • -
              • Issue #3174: Add keyword AUTOCOMMIT on create linked table to control the commit mode -
              • -
              • Issue #3130: Precision of NUMERIC values isn't verified in the Oracle compatibility mode -
              • -
              • Issue #3122: Documentation: Syntax diagram for RENAME CONSTRAINT incorrect -
              • -
              • PR #3129: remove LOB compression -
              • -
              • PR #3127: Cleanups post PageStore removal -
              • -
              • PR #3126: Change nested classes to static nested classes where possible -
              • -
              • PR #3125: Strongly typed LobStorageMap -
              • -
              • PR #3124: Remove PageStore engine -
              • -
              • Issue #3118: SHUTDOWN COMPACT causes 2PC to corrupt database in a simulated crash -
              • -
              • Issue #3115: Infinite loop then OOM in org.h2.mvstore.tx.Transaction.waitFor() when deadlock occurs -
              • -
              • Issue #3113: Data lost when 2 threads read/write TransactionStore and close it normally even if MVStore autoCommit -disabled -
              • -
              • PR #3110: Fix possible int overflow and minor doc change -
              • -
              • Issue #3036: A database that contains BLOBs might grow without being able to be compacted -
              • -
              • Issue #3097: Possible MVStore compaction issue -
              • -
              • PR #3096: Add separate LOB data layer for values -
              • -
              • Issue #3093: ROWNUM filter doesn't work with more than one table -
              • -
              • PR #3087: Add "CONVERT TO CHARACTER SET" to compatibility modes -
              • -
              • Issue #3080: Complex Query returns different results depending on the number of arguments in the IN clause -
              • -
              • Issue #3066: Very high DB opening/closing times -
              • -
              • PR #3077: Add CREATE UNIQUE INDEX ... INCLUDE -
              • -
              • Issue #3061 / PR #3074: GROUP BY using column index for MySQL/MariaDB/PostgreSQL compatibility modes -
              • -
              • PR #3067: Restrict identity data types and result limitation clauses to compatibility modes -
              • -
              • PR #3065: Remove duplicate method IOUtils.getBufferedReader -
              • -
              • Issue #3055: Phantom table leftover after INSERT .. WITH -
              • -
              • PR #3062: Add ALTER DOMAIN RENAME CONSTRAINT command -
              • -
              • Issue #3059: ALTER TABLE DROP CONSTRAINT doesn't check owner of constraint -
              • -
              • Issue #3054: Add binary set aggregate functions -
              • -
              • Issue #3049: Java value getters of ValueNull should throw exceptions -
              • -
              • Issue #3046: SYSTEM_RANGE can't handle bind variable as step size and produces wrong error message -
              • -
              • Issue #3033: NPE during BLOB read after 2PC rollback -
              • -
              • PR #3034: Don't evaluate ValueTimestampTimeZone at begin and end of each command -
              • -
              • PR #3029: Optimize row storage in MVStore and other changes -
              • -
              • PR #3028: Remove back compatibility -
              • -
              • PR #3025: Switch from Travis CI to GitHub Workflows -
              • -
              • PR #3024: Add initial version of upgrade utility -
              • -
              • Issue #3017: ROUND() does not set correct precision and scale of result -
              • -
              • Issue #3003: CREATE TABLE ... AS SELECT ... FROM creates invalid column definition when aggregate functions are used -
              • -
              • Issue #3008: TestCrashAPI: Exception in Arrays.sort() called by LocalResult.done() -
              • -
              • Issue #3006 / PR #3007: Unlock meta during query execution in CREATE TABLE AS query -
              • -
              • PR #3001: PostgreSQL compatibility: UPDATE with FROM -
              • -
              • PR #2998: Fix off-by-one error with -webAdminPassword in Server -
              • -
              • PR #2995: Add FETCH_SIZE clause to CREATE LINKED TABLE -
              • -
              • Issue #2907 / PR #2994: Prevent "Chunk not found" on LOB operations -
              • -
              • PR #2993: Update copyright years -
              • -
              • Issue #2991: TestCrashAPI: NPE in ScriptCommand.dumpDomains() -
              • -
              • Issue #2950 / PR #2987: Issue commit() right before "non-transactional" DDL command starts -
              • -
              • PR #2980: Assorted minor changes -
              • -
              • PR #2966: H2 2.0.201: Linked Tables freeze the Database and freeze the Server Process -
              • -
              • Issue #2972: Memory leak due to negative Page memory in the MVStore -
              • -
              • PR #2971: create skeleton of migration to V2 document -
              • -
              • Issue #2967: MVStore: averageSize int overflow in the class ObjectDataType -
              • -
              • Issue #2963: Syntax error for large hexadecimal constants with DATABASE_TO_UPPER=false -
              • -
              • Issue #2961: Accept CREATE PRIMARY KEY only in metadata or in quirks mode -
              • -
              • Issue #2960: Reject invalid CREATE { UNIQUE | HASH } SPATIAL INDEX -
              • -
              • Issue #2958: TableLink is broken for Oracle database after pull request #2903 -
              • -
              • PR #2955: Prevent incorrect index sorting -
              • -
              • PR #2951: Add documentation for INFORMATION_SCHEMA -
              • -
              • PR #2943: some small prep for next release -
              • -
              • PR #2948: Add support of Infinity, -Infinity, and NaN to DECFLOAT data type -
              • -
              • Issue #2947: Encoding of Unicode and special characters in error messages -
              • -
              • Issue #2891: Fix import of unnamed referential constraints from SQL scripts generated by older versions of H2 -
              • -
              • Issue #2812: Unexpected result for query that compares an integer with a string -
              • -
              • Issue #2936: Add data type conversion code from datetime and UUID values to JSON -
              • -
              • Issue #2935: ENUM ARRAY isn't read properly from persisted data -
              • -
              • Issue #2923: Combination of fileName() with fileStore() should throw an exception -
              • -
              • Issue #2928: JSON_ARRAYAGG and all NULL values -
              • -
              • PR #2918: Removal of unnecessary lock -
              • -
              • Issue #2911: org.h2.mvstore.MVStoreException: Transaction was illegally transitioned from ROLLING_BACK to -ROLLED_BACK -
              • -
              • Issue #1022: JdbcDatabaseMetaData.getPseudoColumns() should be implemented -
              • -
              • Issue #2914: (T1.A = T2.B) OR (T1.A = T2.C) should be optimized to T1.A IN(T2.B, T2.C) to allow index conditions -
              • -
              • PR #2903: Assorted changes -
              • -
              • Issue #2901: PgServer returns less rows when fetchSize is set -
              • -
              • Issue #2894: NPE in DROP SCHEMA when unique constraint is removed before linked referential constraint -
              • -
              • Issue #2888: H2 should pass time zone of client to the server -
              • -
              • PR #2890: Fixed possible eternal wait(0) -
              • -
              • Issue #2846: GRANT SELECT, INSERT, UPDATE, DELETE incorrectly gives privileges to drop a table -
              • -
              • Issue #2882: NPE in UPDATE with SELECT UNION -
              • -
              • PR #2881: Store users and roles together and user-defined functions and aggregates together -
              • -
              • Issue #2878: Disallow spatial indexes in PageStore databases -
              • -
              • PR #2874: Use 64-bit row counts in results and other changes -
              • -
              • Issue #2866: New INFORMATION_SCHEMA should not use keywords as column names -
              • -
              • Issue #2867: PageStore + Lazy + INSERT ... SELECT cause infinite loop -
              • -
              • PR #2869: Normalize binary geometry literals and improve EWKB representation of POLYGON EMPTY -
              • -
              • Issue #2860: CHAR columns in PgCatalogTable have incorrect length -
              • -
              • Issue #2848: Add support for standard <listagg overflow clause> -
              • -
              • Issue #2858: Throw 22001 on attempt to use getString() or getBytes() on LOB object longer than 1,048,576 -chars/octets -
              • -
              • Issue #2854: Define limits for identifiers, number of columns, etc. -
              • -
              • PR #2853: Small optimization for Page compression / decompression -
              • -
              • Issue #2832: Define length limits for non-LOB data types -
              • -
              • Issue #2842: Querying view that uses LTRIM/RTRIM results in a syntax error -
              • -
              • Issue #2841: Call to STRINGDECODE results in StringIndexOutOfBoundsException -
              • -
              • Issue #2839: Querying a view that uses the POSITION() function results in an unexpected syntax error -
              • -
              • Issue #2838: INSERT() with NULL arguments for the original string and string to be added results in NPE -
              • -
              • Issue #2837: ROUND() function should reject invalid number of digits immediately -
              • -
              • Issue #2835: Calling math functions with a string argument results in a NullPointerException -
              • -
              • Issue #2833: MERGE INTO causes an unexpected syntax error -
              • -
              • Issue #2831: Restore YEAR data type for MySQL compatibility mode -
              • -
              • Issue #2822: Suspicious logic in Database.closeImpl() -
              • -
              • Issue #2829: Incorrect manifest entries in sources jar -
              • -
              • Issue #2828: Parser can't parse NOT in simple when operand -
              • -
              • Issue #2826: Table with a generated column cycle results in a NullPointerException -
              • -
              • Issue #2825: Query with % operator results in a ClassCastException -
              • -
              • Issue #2818: TableFilter.getValue() can read value of delegated column faster -
              • -
              • Issue #2816: Query on view that uses the BETWEEN operator results in an unexpected syntax error -
              • -
              • PR #2815: Remove BINARY_COLLATION and UUID_COLLATION settings -
              • -
              • Issue #2813: Query with CASE operator unexpectedly results in "Column must be in the GROUP BY list" error -
              • -
              • Issue #2811: Update build numbers and data format versions -
              • -
              • Issue #2674: OPTIMIZE_IN_SELECT shouldn't convert value to incompatible data types -
              • -
              • Issue #2803: Disallow comparison operations between incomparable data types -
              • -
              • Issue #2561: Separate normal functions and table value functions -
              • -
              • Issue #2804: NPE in ConditionNot.getNotIfPossible() -
              • -
              • Issue #2801: Instances of TableView objects leaking -
              • -
              • PR #2799: Additional bit functions BITNAND, BITNOR, BITXNOR, BITCOUNT, ULSHIFT, URSHIFT, ROTATELEFT, ROTATERIGHT, -BIT_NAND_AGG, BIT_NOR_AGG, and BIT_XNOR_AGG. -
              • -
              • PR #2798: Complete separation of Function class -
              • -
              • Issue #2795: Sporadic issues with trigger during concurrent insert in 1.4.199/1.4.200 -
              • -
              • PR #2796: Assorted refactorings -
              • -
              • Issue #2786: Failure in CREATE TABLE AS leaves inconsistent transaction if some rows were successfully inserted -
              • -
              • Issue #2790: Examples in documentation of CREATE ALIAS should use standard literals only -
              • -
              • Issue #2787: CONCAT and CONCAT_WS functions -
              • -
              • PR #2784: Oracle REGEXP_REPLACE support -
              • -
              • Issue #2780: Remove SCOPE_GENERATED_KEYS setting -
              • -
              • PR #2779: Fix incorrect FK restrictions and other changes -
              • -
              • PR #2778: Assorted changes -
              • -
              • Issue #2776: Referential constraint can create a unique constraint in the wrong schema -
              • -
              • Issue #2771: Add documented DEFAULT ON NULL flag for all types of columns -
              • -
              • Issue #2742 / PR #2768: Better separation of MVStore aimed at smaller h2-mvstore jar -
              • -
              • Issue #2764: Identity columns don't accept large numbers -
              • -
              • IDENTITY() function is removed, SCOPE_IDENTITY() is now available only in MSSQLServer compatibility mode. -
              • -
              • Issue #2757: Intermittent TestFileSystem failures -
              • -
              • Issue #2758: Issues with sequences -
              • -
              • PR #2756: Prevent DROP NOT NULL for identity columns -
              • -
              • Issue #2753: UPDATE statement changes value of GENERATED ALWAYS AS IDENTITY columns -
              • -
              • PR #2751: Add comment explaining seemingly dummy operation -
              • -
              • PR #2750: Use RFC 4122 compliant UUID comparison by default -
              • -
              • PR #2748: PgServer set type text to NULL value -
              • -
              • Issue #2746: Old TCP clients with current server -
              • -
              • PR #2745: PgServer can send bool in binary mode -
              • -
              • PR #2744: Remove jarSmall and jarClient targets -
              • -
              • PR #2743: Add IS_TRIGGER_UPDATABLE and other similar fields to INFORMATION_SCHEMA -
              • -
              • PR #2738: Fix VIEWS.VIEW_DEFINITION and support it for other databases in H2 Console -
              • -
              • PR #2737: Assorted changes -
              • -
              • PR #2734: Update dependencies and fix ResultSetMetaData.isSigned() -
              • -
              • PR #2733: Replace h2.sortNullsHigh with DEFAULT_NULL_ORDERING setting -
              • -
              • PR #2731: Fix spelling errors in German translation -
              • -
              • PR #2728: Add and use DATA_TYPE_SQL() function and remove INFORMATION_SCHEMA.PARAMETERS.REMARKS -
              • -
              • Issue #1015: ENUM and arithmetic operators -
              • -
              • Issue #2711: Store normalized names of data types in metadata -
              • -
              • PR #2722: Implement getRowCount() for some INFORMATION_SCHEMA tables -
              • -
              • PR #2721: Improve LOCKS, SESSIONS, and USERS and optimize COUNT(*) on other isolation levels in some cases -
              • -
              • Issue #2655: TestCrashAPI: AssertionError at MVPrimaryIndex.<init> -
              • -
              • Issue #2716: Fix URL of Maven repository -
              • -
              • Issue #2715: Mention `DB_CLOSE_DELAY=-1` flag in JDBC URL on the "Cheat Sheet" page -
              • -
              • PR #2714: fixed few code smells discovered by PVS-Studio -
              • -
              • Issue #2712: `NOT LIKE` to a sub-query doesn't work -
              • -
              • PR #2710: PgServer: set oid and attnum in RowDescription -
              • -
              • Issue #2254: Add standard DECFLOAT data type -
              • -
              • PR #2708: Add declared data type attributes to the INFORMATION_SCHEMA -
              • -
              • Issue #2706: Empty comments / remarks on objects -
              • -
              • PR #2705: Return standard-compliant DATA_TYPE for strings -
              • -
              • PR #2703: Fix case-insensitive comparison issues with national characters -
              • -
              • Issue #2701: Subquery with FETCH should not accept global conditions -
              • -
              • Issue #2699: Remove FUNCTIONS_IN_SCHEMA setting -
              • -
              • Issue #452: Add possibility to use user-defined aggregate functions with schema -
              • -
              • PR #2695: Refactor handling of parentheses in getSQL() methods -
              • -
              • PR #2693: disallow VARCHAR_IGNORECASE in PostgreSQL mode -
              • -
              • Issue #2407: Implement CHAR whitespace handling correctly -
              • -
              • PR #2685: Check existing data in ALTER DOMAIN ADD CONSTRAINT -
              • -
              • PR #2683: Fix data types in Transfer -
              • -
              • PR #2681: Report user functions in standard ROUTINES and PARAMETERS views -
              • -
              • PR #2680: Reimplement remaining DatabaseMetaData methods and fix precision of binary numeric types -
              • -
              • PR #2679: Reimplement getTables(), getTableTypes(), and getColumns() -
              • -
              • PR #2678: Reimplement getPrimaryKeys(), getBestRowIdentifier(), getIndexInfo() and others -
              • -
              • PR #2675: Reimplement getImportedKeys(), getExportedKeys(), and getCrossReferences() -
              • -
              • PR #2673: Reimplement some metadata methods -
              • -
              • PR #2672: Forward DatabaseMetaData calls to server -
              • -
              • Issue #2329: Content of INFORMATION_SCHEMA should be listed as VIEWS -
              • -
              • PR #2668: Sequence generator data type option and length parameter for JSON data type -
              • -
              • PR #2666: Add ALTER DOMAIN RENAME command -
              • -
              • PR #2663: Add ALTER DOMAIN { SET | DROP } { DEFAULT | ON UPDATE } -
              • -
              • PR #2661: Don't allow construction of incomplete ARRAY and ROW data types -
              • -
              • Issue #2659: NULLIF with row values -
              • -
              • PR #2658: Extract date-time and some other groups of functions into own classes -
              • -
              • PR #2656: add `_int2` and `_int4` for PgServer -
              • -
              • PR #2654: Move out JSON, cardinality, ABS, MOD, FLOOR, and CEIL functions from the Function class -
              • -
              • PR #2653: Use full TypeInfo for conversions between PG and H2 data types -
              • -
              • PR #2652: Add "SHOW ALL" -
              • -
              • PR #2651: add `pg_type.typelem` and `pg_type.typdelim` -
              • -
              • PR #2649: Extract some groups of functions from Function class -
              • -
              • PR #2646: Add some PostgreSQL compatibility features -
              • -
              • PR #2645: Add CURRENT_PATH, CURRENT_ROLE, SESSION_USER, and SYSTEM_USER -
              • -
              • Issue #2643: Send PG_TYPE_TEXTARRAY values to ODBC drivers properly -
              • -
              • PR #2642: Throw proper exceptions from array element reference and TRIM_ARRAY -
              • -
              • PR #2640: German translations -
              • -
              • Issue #2108: Add possible candidates in different case to table not found exception -
              • -
              • Issue #2633: Multi-column UPDATE assignment needs to be reimplemented -
              • -
              • PR #2635: Implement REGEXP_SUBSTR function -
              • -
              • PR #2632: Improve ROW data type -
              • -
              • PR #2630: fix: quoted VALUE in documentation -
              • -
              • Issue #2628: Cached SQL throws JdbcSQLSyntaxErrorException if executed with different parameter values than before -
              • -
              • Issue #2611: Add quantified distinct predicate -
              • -
              • Issue #2620: LOBs in triggers -
              • -
              • PR #2619: ARRAY_MAX_CARDINALITY and TRIM_ARRAY functions -
              • -
              • PR #2617: Add Feature F262: Extended CASE expression -
              • -
              • PR #2615: Add feature T461: Symmetric BETWEEN predicate -
              • -
              • PR #2614: Fix support of multi-dimensional arrays in Java functions -
              • -
              • Issue #2608: Improve concatenation operation for multiple operands -
              • -
              • PR #2605: Assorted minor changes -
              • -
              • Issue #2602: H2 doesn't allow to create trigger from Java source code if there are nested classes -
              • -
              • PR #2601: Add field SLEEP_SINCE to INFORMATION_SCHEMA.SESSIONS table -
              • -
              • Issue #1973: Standard MERGE statement doesn't work with views -
              • -
              • Issue #2552: MERGE statement should process each row only once -
              • -
              • Issue #2548: Wrong update count when MERGE statement visits matched rows more than once -
              • -
              • Issue #2394: H2 does not accept DCL after source merge table -
              • -
              • Issue #2196: Standard MERGE statement doesn't release the source view -
              • -
              • Issue #2567: ARRAY-returning Java functions don't return the proper data type -
              • -
              • Issue #2584: Regression in NULL handling in multiple AND or OR conditions -
              • -
              • PR #2577: PgServer: `array_to_string()` and `set join_collapse_limit` -
              • -
              • PR #2568: Add BIT_XOR_AGG aggregate function -
              • -
              • PR #2565: Assorted minor changes -
              • -
              • PR #2563: defrag is not contributing much, remove from test run -
              • -
              • PR #2562: new exception MVStoreException -
              • -
              • PR #2557: don't throw IllegalStateException in checkOpen -
              • -
              • PR #2554: Reenable mvstore TestCrashAPI -
              • -
              • Issue #2556: TestOutOfMemory: Table "STUFF" not found -
              • -
              • PR #2555: Move current datetime value functions into own class -
              • -
              • PR #2547: split up the ValueLob classes -
              • -
              • PR #2542: Pipelining mvstore chunk creation / save -
              • -
              • Issue #2550: NullPointerException with MERGE containing unknown column in AND condition of WHEN -
              • -
              • Issue #2546: Disallow empty CASE specifications and END CASE -
              • -
              • Issue #2530: Long query with many AND expressions causes StackOverflowError -
              • -
              • PR #2543: Improve case specification support and fix some issues with it -
              • -
              • Issue #2539: Replace non-standard functions with standard code directly in Parser -
              • -
              • Issue #2521: Disallow untyped arrays -
              • -
              • Issue #2532: Duplicate column names in derived table should be acceptable in the presence of a derived column list -that removes ambiguities -
              • -
              • PR #2527: Feature: allow @ meta commands from Console -
              • -
              • PR #2526: Reduce I/O during database presence check and restrict some compatibility settings to their modes -
              • -
              • PR #2525: Restore support of third-party drivers in the Shell tool -
              • -
              • Issue #1710: getHigherType() returns incorrect type for some arguments -
              • -
              • PR #2516: SHUTDOWN IMMEDIATELY should be a normal shut down -
              • -
              • PR #2515: Fix nested comments in ScriptReader -
              • -
              • Issue #2511: Restrict Oracle compatibility functions to Oracle compatibility mode -
              • -
              • PR #2508: Minor refactoring around Tx isolation level -
              • -
              • PR #2505: Assorted changes in DATEADD, DATEDIFF, DATE_TRUNC, and EXTRACT -
              • -
              • Issue #2502: Combination of DML with data change delta table skips subsequent update -
              • -
              • PR #2499: Performance fix for PageStore under concurrent load -
              • -
              • PR #2498: Add some PostgreSQL compatibility features mentioned in issue #2450 -
              • -
              • Issue #2496: Error when using empty JSON_OBJECT() or JSON_ARRAY() functions -
              • -
              • PR #2495: Fix JSON_OBJECT grammar in documentation -
              • -
              • Issue #2493 / PR #2494: Replace ColumnNamer with mode-specific generation of column names for views -
              • -
              • PR #2492: Assorted changes in parser, keywords, and ILIKE condition -
              • -
              • PR #2490: Replace pg_catalog.sql with PgCatalogTable and use DATABASE_TO_LOWER in PG Server -
              • -
              • Issue #2488 / PR #2489: Mark version functions as not deterministic -
              • -
              • Issue #2481: Convert TO to keyword -
              • -
              • PR #2476: Add some PostgreSQL compatibility features mentioned in issue #2450 -
              • -
              • PR #2479: Recognize absolute path on Windows without drive letter -
              • -
              • Issue #2475: Select order by clause is exported with non-portable SQL -
              • -
              • Issue #2472: Updating column to empty string in Oracle mode with prepared statement does not result in null -
              • -
              • PR #2468: MVStore scalability improvements -
              • -
              • PR #2466: Add partial support for MySQL COLLATE and CHARACTER statements -
              • -
              • Issue #2464: `client_encoding='utf-8'` (single quoted) from `node-postgres` not recognized -
              • -
              • Issue #2461: Support for binary_float and binary_double type aliases -
              • -
              • Issue #2460: Exception when accessing empty arrays -
              • -
              • Issue #2318: Remove incorrect rows from DatabaseMetaData.getTypeInfo() and INFORMATION_SCHEMA.TYPE_INFO -
              • -
              • Issue #2455: `bytea` column incorrectly read by `psycopg2` -
              • -
              • PR #2456: Add standard array value constructor by query -
              • -
              • PR #2451: Add some PostgreSQL compatibility features mentioned in issue #2450 -
              • -
              • Issue #2448: Change default data type name from DOUBLE to DOUBLE PRECISION -
              • -
              • PR #2452: Do not use unsafe and unnecessary FROM DUAL internally -
              • -
              • PR #2449: Add support for standard trigraphs -
              • -
              • Issue #2439: StringIndexOutOfBoundsException when using TO_CHAR -
              • -
              • Issue #2444: WHEN NOT MATCHED THEN INSERT should accept only one row -
              • -
              • Issue #2434: Next value expression should return the same value within a processed row -
              • -
              • PR #2437: Assorted changes in MVStore -
              • -
              • Issue #2430: Postgres `bytea` column should be read with and without `forceBinary` -
              • -
              • Issue #2267: BINARY and VARBINARY should be different -
              • -
              • Issue #2266: CHAR and BINARY should have length 1 by default -
              • -
              • PR #2426: Add MD5 and all SHA-1, SHA-2, and SHA-3 digests to the HASH() function -
              • -
              • Issue #2424: 0 should not be accepted as a length of data type -
              • -
              • Issue #2378: JAVA_OBJECT and TableLink -
              • -
              • Issue #2417: Casts between binary strings and non-string data types -
              • -
              • Issue #2416: OTHER and JAVA_OBJECT -
              • -
              • Issue #2379: SQL export can change data type of a constant -
              • -
              • Issue #2411: ArrayIndexOutOfBoundsException when HAVING and duplicate columns in SELECT -
              • -
              • Issue #2194: Add own enumeration of data types to API -
              • -
              • PR #2408: Descending MVMap and TransactionMap cursor -
              • -
              • Issue #2399: Cast to ARRAY with a nested ARRAY does not check the maximum cardinality of the nested ARRAY -
              • -
              • Issue #2402: Remove old ValueLob and DbUpgrade -
              • -
              • Issue #2400: Inconsistent data type conversion between strings and LOBs -
              • -
              • PR #2398: Add expandable flags for SQL generation methods -
              • -
              • PR #2395: Fix for two recent page format bugs -
              • -
              • PR #2386: Chunk occupancy mask -
              • -
              • PR #2385: Memory estimate -
              • -
              • PR #2381: Follow up REPEATABLE_READ-related changes -
              • -
              • PR #2380: use JIRA tracker URLs for JDK bugs -
              • -
              • PR #2376: Fix IN condition with row value expressions in its right side -
              • -
              • Issue #2367 / PR #2370: fix backward compatibility with 1.4.200 -
              • -
              • Issue #2371: REPEATABLE READ isolation level does not work in MVStore -
              • -
              • Issue #2363: Soft links in -baseDir and database path cause error 90028 -
              • -
              • Issue #2364: TestScript datatypes/timestamp-with-time-zone.sql fails if TZ=Europe/Berlin -
              • -
              • Issue #2359: Complete implementation of generated columns -
              • -
              • PR #2361: Fix unused result -
              • -
              • PR #2353: Push binary search operation from Page to DataType -
              • -
              • Issue #2348: Add USING clause to ALTER COLUMN CHANGE DATA TYPE -
              • -
              • Issue #2350: License Problem in POM -
              • -
              • Issue #2345: Add standard SET TIME ZONE command to set current time zone of the session -
              • -
              • PR #2341: Cleanup file backend sync -
              • -
              • Issue #2343: Domain-based domains: Domain not found after reconnection -
              • -
              • Issue #2338: Domains should not support NULL constraints -
              • -
              • Issue #2334: build target mavenInstallLocal broken since commit 7cbbd55e -
              • -
              • #2335: TestDateTimeUtils fails if system timezone has DST in the future -
              • -
              • Issue #2330: Syntax error with parenthesized expression in GROUP BY clause -
              • -
              • Issue #2256: <interval value expression> with datetime subtraction -
              • -
              • Issue #2325: H2 does not parse nested bracketed comments correctly -
              • -
              • Issue #466: Confusion about INFORMATION_SCHEMA content related to UNIQUE constraints -
              • -
              • PR #2323: Assorted changes -
              • -
              • Issue #2320: Remove SAMPLE_SIZE clause from SELECT -
              • -
              • Issue #2301: Add compatibility setting to accept some keywords as identifiers -
              • -
              • PR #2317: Replace CHECK_COLUMN_USAGE with CONSTRAINT_COLUMN_USAGE and other changes -
              • -
              • Issue #2315: Sequence must remember its original START WITH value -
              • -
              • Issue #2313: DISTINCT does not work in ordered aggregate functions -
              • -
              • PR #2306: Add support for RESTART of sequence without initial value -
              • -
              • Issue #2304: NPE in multiple define commands in one statement after upgrade from H2 4.1.197 -
              • -
              • PR #2303: Assorted minor changes -
              • -
              • Issue #2286: Inline check constraints not in INFORMATION_SCHEMA -
              • -
              • PR #2300: Continue generification of MVStore codebase -
              • -
              • PR #2298: add some minimal security documentation -
              • -
              • PR #2292: synchronize fileBase subclasses use of position -
              • -
              • PR #2238: Some MVStore refactoring -
              • -
              • Issue #2288: ConcurrentModificationException during commit -
              • -
              • Issue #2293: Remove TestClearReferences and workarounds for old versions of Apache Tomcat -
              • -
              • Issue #2288: ConcurrentModificationException during commit -
              • -
              • PR #2284: Remove unrelated information from README and add some information about H2 -
              • -
              • PR #2282: add PostgreSQL compatible variable STATEMENT_TIMEOUT -
              • -
              • PR #2280: little comment -
              • -
              • Issue #2205: H2 1.4.200 split FS issue -
              • -
              • Issue #2272: UpdatableView and obtaining the Generated Keys -
              • -
              • PR #2276: Split up filesystem classes -
              • -
              • PR #2275: improve detection of JAVA_HOME on Mac OS -
              • -
              • Issue #2268: Numeric division needs better algorithm for scale selection -
              • -
              • Issue #2270: IGNORE_UNKNOWN_SETTINGS is ignored -
              • -
              • PR #2269: Fix existence check of non-persistent databases -
              • -
              • Issue #1910: BinaryOperation should evaluate precision and scale properly -
              • -
              • PR #2264: Clean up redundant parts of file system abstraction -
              • -
              • PR #2262: add setting AUTO_COMPACT_FILL_RATE -
              • -
              • Issue #2255 / PR #2259: Use NIO2 in main sources and build -
              • -
              • PR #2257: Catch java.lang.NoClassDefFoundError -
              • -
              • Issue #2241: Mark H2-specific and compatibility only clauses in documentation -
              • -
              • PR #2246: Update third-party drivers -
              • -
              • Issue #2239 / PR #2236: Add NETWORK_TIMEOUT setting for SO_TIMEOUT -
              • -
              • PR #2235: Don't use RandomAccessFile in FilePathNio -
              • -
              • Issue #2233: "Prepared.getObjectId() was called before" when granting on multiple tables -
              • -
              • PR #2230: Add factory methods for Row -
              • -
              • Issue #2226, PR #2227: Remove support of Apache Ignite -
              • -
              • PR #2224: Update some hyperlinks and use https in them where possible -
              • -
              • PR #2223: Fix data change delta tables in views -
              • -
              • Issue #1943: Deadlock in TestTriggersConstraints -
              • -
              • PR #2219: do not retry failed DDL commands -
              • -
              • PR #2214: Fix TRACE_LEVEL_FILE=4 for in-memory databases -
              • -
              • PR #2216: Add FileChannel.lock in the connection URL summary -
              • -
              • PR #2215: Add white-space: pre to tables with query results -
              • -
              • Issue #2213: NUMERIC scale can be larger than a precision -
              • -
              • PR #2212: Get rid of multi-version CurrentTimestamp and fix negative scale of NUMERIC -
              • -
              • PR #2210: Meta table extras -
              • -
              • PR #2209: Add standard expressions with interval qualifier -
              • -
              • PR #2195: Feature abort_session function -
              • -
              • PR #2201: Add padding to negative years and other changes -
              • -
              • PR #2197: Add some additional methods from JDBC 4.2 and return 4.2 as supported version -
              • -
              • PR #2193: Require Java 8 and remove Java 7 support -
              • -
              • Issue #2191: NPE with H2 v1.4.200 repeatable read select queries -
              • -
              • Issue #1390: Add standard-compliant ARRAY data type syntax -
              • -
              • PR #2186: Refactor Parser.parseColumnWithType() and fix some minor issues with CAST -
              • -
              • Issue #2181: SET EXCLUSIVE quirks -
              • -
              • PR #2173: Move snapshots from Transaction to TransactionMap -
              • -
              • Issue #2175: Regression: NPE in ResultSet#getTime(int) -
              • -
              • Issue #2171: Wrong PostgreSQL compatibility syntax for the creation of indexes -
              • -
              • PR #2169: Clean up some find methods of indexes and fix minor issues with them -
              • -
          2.1.220Windows InstallerPlatform-Independent Zip
          2.1.212Windows InstallerPlatform-Independent Zip
          2.1.210 Windows Installer +++++++
          FETCH +++++++
          FILTERCS++++
          FOR +++++++
          FOREIGN
          diff --git a/h2/src/docsrc/html/download-archive.html b/h2/src/docsrc/html/download-archive.html index 730287d723..4be76a6f8a 100644 --- a/h2/src/docsrc/html/download-archive.html +++ b/h2/src/docsrc/html/download-archive.html @@ -28,6 +28,10 @@

          Distribution

          + + + + diff --git a/h2/src/docsrc/html/download.html b/h2/src/docsrc/html/download.html index a0f1a00f33..02609b78fb 100644 --- a/h2/src/docsrc/html/download.html +++ b/h2/src/docsrc/html/download.html @@ -29,10 +29,10 @@

          Version ${version} (${versionDate})

          Version 2.1.210 (2022-01-17)

          -Windows Installer -(SHA1 checksum: ff795bf6ccefd5950d5080b596d835d13206b325)
          -Platform-Independent Zip -(SHA1 checksum: 6ede99a0a987971557e878de4eddcb796d604323)
          +Windows Installer +(SHA1 checksum: 06664cf7ae51b19208ccbe7eef2969d35c6366dc)
          +Platform-Independent Zip +(SHA1 checksum: 17e1f685eb112e710d652aed0135eca8bfa78180)

          Archive Downloads

          diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index f69e3b2996..a128107321 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -15,18 +15,18 @@ public class Constants { /** * The build date is updated for each public release. */ - public static final String BUILD_DATE = "2022-04-09"; + public static final String BUILD_DATE = "2022-06-13"; /** * Sequential version number. Even numbers are used for official releases, * odd numbers are used for development builds. */ - public static final int BUILD_ID = 219; + public static final int BUILD_ID = 214; /** * Whether this is a snapshot version. */ - public static final boolean BUILD_SNAPSHOT = true; + public static final boolean BUILD_SNAPSHOT = false; /** * If H2 is compiled to be included in a product, this should be set to diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index fd1335d95e..7b71992492 100644 --- a/h2/src/main/org/h2/mvstore/db/Store.java +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -85,6 +85,7 @@ static char[] decodePassword(byte[] key) { * Creates the store. * * @param db the database + * @param key for file encryption */ public Store(Database db, byte[] key) { String dbPath = db.getDatabasePath(); diff --git a/h2/src/test/org/h2/samples/newsfeed.sql b/h2/src/test/org/h2/samples/newsfeed.sql index 60f9a1060c..e00f43466c 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 +(156, '2.1.214', '2022-06-13'), (155, '2.1.212', '2022-04-09'), (154, '2.1.210', '2022-01-17'), (153, '2.0.206', '2022-01-04'), @@ -19,8 +20,7 @@ INSERT INTO VERSION VALUES (146, '1.4.196', '2017-06-10'), (145, '1.4.195', '2017-04-23'), (144, '1.4.194', '2017-03-10'), -(143, '1.4.193', '2016-10-31'), -(142, '1.4.192', '2016-05-26'); +(143, '1.4.193', '2016-10-31'); CREATE TABLE CHANNEL(TITLE VARCHAR, LINK VARCHAR, DESC VARCHAR, LANGUAGE VARCHAR, PUB TIMESTAMP, LAST TIMESTAMP, AUTHOR VARCHAR); From bbb5a590b91597649b19f81a5f89ecb4bf44d33d Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 13 Jun 2022 21:28:37 -0400 Subject: [PATCH 92/93] release preparation - minor version --- h2/src/main/org/h2/engine/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java index a128107321..0e42e582c2 100644 --- a/h2/src/main/org/h2/engine/Constants.java +++ b/h2/src/main/org/h2/engine/Constants.java @@ -78,7 +78,7 @@ public class Constants { /** * The minor version of this database. */ - public static final int VERSION_MINOR = 2; + public static final int VERSION_MINOR = 1; /** * The lock mode that means no locking is used at all. From 1ba3590b5d29581a14b018b966e5da0a8ff2994c Mon Sep 17 00:00:00 2001 From: Andrei Tokar Date: Mon, 13 Jun 2022 21:32:55 -0400 Subject: [PATCH 93/93] release preparation - another minor adjustment --- h2/src/docsrc/html/download.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2/src/docsrc/html/download.html b/h2/src/docsrc/html/download.html index 02609b78fb..3b1763648c 100644 --- a/h2/src/docsrc/html/download.html +++ b/h2/src/docsrc/html/download.html @@ -27,7 +27,7 @@

          Version ${version} (${versionDate})


          -

          Version 2.1.210 (2022-01-17)

          +

          Version 2.1.212 (2022-04-09)

          Windows Installer (SHA1 checksum: 06664cf7ae51b19208ccbe7eef2969d35c6366dc)

          2.1.214Windows InstallerPlatform-Independent Zip
          2.1.212 Windows Installer Platform-Independent Zip