diff --git a/README.md b/README.md index 70de378686..715833bdb5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ More information: https://h2database.com com.h2database h2 - 2.1.210 + 2.1.214 ``` 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/pom.xml b/h2/pom.xml index a0c0085569..9476dbf837 100644 --- a/h2/pom.xml +++ b/h2/pom.xml @@ -4,7 +4,7 @@ com.h2database h2 - 2.1.210 + 2.2.219-SNAPSHOT jar H2 Database Engine https://h2database.com @@ -44,7 +44,7 @@ 5.6.2 8.5.2 5.0.0 - 42.2.14 + 42.4.0 4.0.1 5.0.0 1.7.30 diff --git a/h2/src/docsrc/html/advanced.html b/h2/src/docsrc/html/advanced.html index 68e865b1ff..05b0b96581 100644 --- a/h2/src/docsrc/html/advanced.html +++ b/h2/src/docsrc/html/advanced.html @@ -529,8 +529,6 @@

Keywords / Reserved Words

+++++++ FETCH +++++++ -FILTER -CS++++ FOR +++++++ FOREIGN @@ -1813,9 +1811,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/changelog.html b/h2/src/docsrc/html/changelog.html index 23c1e63e38..b0f75e9a0c 100644 --- a/h2/src/docsrc/html/changelog.html +++ b/h2/src/docsrc/html/changelog.html @@ -21,7 +21,88 @@

    Change Log

    Next Version (unreleased)

    + +

    Version 2.1.214 (2022-06-13)

    + + +

    Version 2.1.212 (2022-04-09)

    + @@ -149,1103 +230,5 @@

    Version 2.0.204 (2021-12-21)

  • -

    Version 2.0.202 (2021-11-25)

    - - -

    Version 1.4.200 (2019-10-14)

    - diff --git a/h2/src/docsrc/html/download-archive.html b/h2/src/docsrc/html/download-archive.html index 09b4b11a50..4be76a6f8a 100644 --- a/h2/src/docsrc/html/download-archive.html +++ b/h2/src/docsrc/html/download-archive.html @@ -28,6 +28,14 @@

    Distribution

    + + + + + + + + diff --git a/h2/src/docsrc/html/download.html b/h2/src/docsrc/html/download.html index 768c2ea78c..3b1763648c 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.212 (2022-04-09)

    -Windows Installer -(SHA1 checksum: 982dff9c88412b00b3ced52b6870753e0133be07)
    -Platform-Independent Zip -(SHA1 checksum: 85d6d8f552661c2f8e1b86c10a12ab4bb6b0d29b)
    +Windows Installer +(SHA1 checksum: 06664cf7ae51b19208ccbe7eef2969d35c6366dc)
    +Platform-Independent Zip +(SHA1 checksum: 17e1f685eb112e710d652aed0135eca8bfa78180)

    Archive Downloads

    diff --git a/h2/src/docsrc/html/features.html b/h2/src/docsrc/html/features.html index 5d1f7c7f22..c3f387dfdd 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

    @@ -898,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. @@ -929,6 +931,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 +1061,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

    @@ -1085,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/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/docsrc/html/performance.html b/h2/src/docsrc/html/performance.html index 54d1b4ba15..0bb71c8ea7 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 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. +

    +

    +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/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/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/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 6aa8a51d37..36c0cfbb1b 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;
    @@ -366,6 +367,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;
    @@ -447,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;
    @@ -540,6 +542,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.
          *
    @@ -586,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) {
    @@ -641,33 +658,31 @@ 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);
                 }
             }
    -        p.setPrepareAlways(recompileAlways);
    -        p.setParameterList(parameters);
             return p;
         }
     
    -    private Prepared parse(String sql, boolean withExpectedList) {
    +    private Prepared parse(boolean withExpectedList) {
             if (withExpectedList) {
                 expectedList = new ArrayList<>();
             } 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 {
    @@ -679,6 +694,8 @@ private Prepared parse(String sql, boolean withExpectedList) {
                 }
                 throw t;
             }
    +        p.setPrepareAlways(recompileAlways);
    +        p.setParameterList(parameters);
             return p;
         }
     
    @@ -848,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 {
    @@ -875,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);
    @@ -952,8 +962,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 {
    @@ -1363,7 +1372,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");
             }
    @@ -1537,8 +1546,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)) {
    @@ -1645,8 +1653,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;
                     }
    @@ -1670,14 +1677,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;
         }
    @@ -1699,10 +1702,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)) {
    @@ -1728,10 +1728,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);
                 }
             }
    @@ -1771,8 +1768,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);
    @@ -1804,12 +1800,13 @@ 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);
    -        } else if (readIf(TABLE)) {
    +            table = query.toTable(alias, null, getUsedParameters(outerUsedParameters), createView != null,
    +                    currentSelect);
    +        } 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 {
    @@ -1819,7 +1816,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;
                 }
    @@ -1895,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;
    @@ -1903,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);
    @@ -1912,17 +1911,17 @@ 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]);
                     }
    -                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);
    @@ -1946,7 +1945,6 @@ private TableFilter buildTableFilter(Table table, String alias, ArrayList readDerivedColumnNames() {
         }
     
         private void discardWithTableHints() {
    -        if (readIf(WITH)) {
    -            read(OPEN_PAREN);
    +        if (readIf(WITH, OPEN_PAREN)) {
                 do {
                     discardTableHint();
                 } while (readIfMore());
    @@ -2095,11 +2092,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);
    @@ -2109,8 +2104,7 @@ private Prepared parseTruncate() {
         }
     
         private boolean readIfExists(boolean ifExists) {
    -        if (readIf(IF)) {
    -            read(EXISTS);
    +        if (readIf(IF, EXISTS)) {
                 ifExists = true;
             }
             return ifExists;
    @@ -2289,12 +2283,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;
    @@ -2414,8 +2406,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),
    @@ -2572,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;
    @@ -2655,8 +2638,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;
    @@ -2709,8 +2691,7 @@ private void parseEndOfQuery(Query command) {
                             read("ROWS");
                         }
                     }
    -                if (readIf(WITH)) {
    -                    read("TIES");
    +                if (readIf(WITH, "TIES")) {
                         command.setWithTies(true);
                     } else {
                         read("ONLY");
    @@ -2757,9 +2738,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
    @@ -2833,15 +2812,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());
    @@ -2888,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);
    @@ -2905,8 +2883,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 {
    @@ -2961,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);
    @@ -3140,17 +3117,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);
    @@ -3321,12 +3293,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);
    @@ -3341,8 +3311,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);
    @@ -3351,21 +3320,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;
    @@ -3652,8 +3627,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;
    @@ -3710,9 +3684,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);
    @@ -3750,8 +3722,7 @@ private Window readWindowSpecification() {
                 }
             }
             ArrayList partitionBy = null;
    -        if (readIf("PARTITION")) {
    -            read("BY");
    +        if (readIf("PARTITION", "BY")) {
                 partitionBy = Utils.newSmallArrayList();
                 do {
                     Expression expr = readExpression();
    @@ -3787,8 +3758,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;
    @@ -3957,16 +3927,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 +3988,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();
    @@ -4034,9 +4015,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);
    @@ -4504,8 +4485,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);
    @@ -4712,61 +4692,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) {
    @@ -4826,8 +4783,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;
    @@ -4862,7 +4818,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);
    @@ -4915,31 +4871,72 @@ 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();
    +        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));
    +            }
             }
    -        if (index > Constants.MAX_PARAMETER_INDEX) {
    -            throw DbException.getInvalidValueException("parameter index", index);
    +        outerUsedParameters.or(innerUsedParameters);
    +        usedParameters = outerUsedParameters;
    +        return params;
    +    }
    +
    +    private Expression readTerm() {
    +        Expression r = currentTokenType == IDENTIFIER ? readTermWithIdentifier() : readTermWithoutIdentifier();
    +        if (readIf(OPEN_BRACKET)) {
    +            r = new ArrayElementReference(r, readExpression());
    +            read(CLOSE_BRACKET);
             }
    -        index--;
    -        if (parameters.size() <= index) {
    -            parameters.ensureCapacity(index + 1);
    -            while (parameters.size() < index) {
    -                parameters.add(null);
    +        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);
    +                }
                 }
    -            p = new Parameter(index);
    -            parameters.add(p);
    -        } else if ((p = parameters.get(index)) == null) {
    -            p = new Parameter(index);
    -            parameters.set(index, p);
    +            break;
             }
    -        return p;
    +        return r;
         }
     
    -    private Expression readTerm() {
    +    private Expression readTermWithoutIdentifier() {
             Expression r;
             switch (currentTokenType) {
             case AT:
    @@ -5036,20 +5033,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;
    @@ -5070,7 +5068,7 @@ private Expression readTerm() {
                 if (currentSelect == null && currentPrepared == null) {
                     throw getSyntaxError();
                 }
    -            r = new Rownum(getCurrentPrepared());
    +            r = new Rownum(getCurrentPreparedOrSelect());
                 break;
             case NULL:
                 read();
    @@ -5112,17 +5110,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);
    @@ -5134,16 +5136,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:
    @@ -5190,68 +5196,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) {
    @@ -5305,11 +5283,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);
                     }
    @@ -5367,18 +5343,14 @@ && equalsToken("E", name)) {
                 break;
             case 'N':
                 if (equalsToken("NEXT", name)) {
    -                int index = tokenIndex;
    -                if (readIf(VALUE) && readIf(FOR)) {
    -                    return new SequenceValue(readSequence(), getCurrentPrepared());
    +                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();
                         }
    @@ -5386,11 +5358,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();
    @@ -5400,9 +5368,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();
                         }
    @@ -5410,11 +5376,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();
    @@ -5437,7 +5399,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));
    @@ -5447,8 +5409,9 @@ && equalsToken("E", 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() {
    @@ -5462,78 +5425,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);
             }
    @@ -5542,9 +5441,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);
                 }
    @@ -5765,7 +5662,7 @@ private String readIdentifier() {
         }
     
         private void read(String expected) {
    -        if (token.isQuoted() || !equalsToken(expected, currentToken)) {
    +        if (!testToken(expected, token)) {
                 addExpected(expected);
                 throw getSyntaxError();
             }
    @@ -5781,7 +5678,7 @@ private void read(int tokenType) {
         }
     
         private boolean readIf(String tokenName) {
    -        if (!token.isQuoted() && equalsToken(tokenName, currentToken)) {
    +        if (testToken(tokenName, token)) {
                 read();
                 return true;
             }
    @@ -5789,6 +5686,26 @@ private boolean readIf(String tokenName) {
             return false;
         }
     
    +    private boolean readIf(String tokenName1, String tokenName2) {
    +        int i = tokenIndex + 1;
    +        if (i + 1 < tokens.size() && testToken(tokenName1, token) && testToken(tokenName2, tokens.get(i))) {
    +            setTokenIndex(i + 1);
    +            return true;
    +        }
    +        addExpected(tokenName1, tokenName2);
    +        return false;
    +    }
    +
    +    private boolean readIf(String tokenName1, int tokenType2) {
    +        int i = tokenIndex + 1;
    +        if (i + 1 < tokens.size() && tokens.get(i).tokenType() == tokenType2 && testToken(tokenName1, token)) {
    +            setTokenIndex(i + 1);
    +            return true;
    +        }
    +        addExpected(tokenName1, TOKENS[tokenType2]);
    +        return false;
    +    }
    +
         private boolean readIf(int tokenType) {
             if (tokenType == currentTokenType) {
                 read();
    @@ -5798,14 +5715,67 @@ private boolean readIf(int tokenType) {
             return false;
         }
     
    +    private boolean readIf(int tokenType1, int tokenType2) {
    +        if (tokenType1 == currentTokenType) {
    +            int i = tokenIndex + 1;
    +            if (tokens.get(i).tokenType() == tokenType2) {
    +                setTokenIndex(i + 1);
    +                return true;
    +            }
    +        }
    +        addExpected(tokenType1, tokenType2);
    +        return false;
    +    }
    +
    +    private boolean readIf(int tokenType1, String tokenName2) {
    +        if (tokenType1 == currentTokenType) {
    +            int i = tokenIndex + 1;
    +            if (testToken(tokenName2, tokens.get(i))) {
    +                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)) {
    +        if (testToken(tokenName, token)) {
                 return true;
             }
             addExpected(tokenName);
             return false;
         }
     
    +    private boolean testToken(Object expected, Token token) {
    +        return expected instanceof Integer ? (int) expected == token.tokenType() : testToken((String) expected, token);
    +    }
    +
    +    private boolean testToken(String tokenName, Token token) {
    +        if (!token.isQuoted()) {
    +            String s = token.asIdentifier();
    +            return identifiersToUpper ? tokenName.equals(s) : tokenName.equalsIgnoreCase(s);
    +        }
    +        return false;
    +    }
    +
         private boolean isToken(int tokenType) {
             if (tokenType == currentTokenType) {
                 return true;
    @@ -5837,6 +5807,28 @@ private void addExpected(int tokenType) {
             }
         }
     
    +    private void addExpected(int tokenType1, int tokenType2) {
    +        if (expectedList != null) {
    +            expectedList.add(TOKENS[tokenType1] + ' ' + TOKENS[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]);
    @@ -5879,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();
         }
     
    @@ -5942,8 +5958,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;
                     }
    @@ -5969,8 +5984,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);
    @@ -5998,9 +6012,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;
    @@ -6152,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);
    @@ -6288,7 +6303,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.
    @@ -6298,9 +6314,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);
                 }
             }
    @@ -6359,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);
         }
    @@ -6391,13 +6407,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);
         }
    @@ -6417,13 +6430,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);
         }
    @@ -6456,8 +6466,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;
    @@ -6535,8 +6544,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);
    @@ -6717,8 +6725,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");
    @@ -6752,15 +6759,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);
                 }
    @@ -6785,8 +6790,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;
                     }
    @@ -6953,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;
             }
    @@ -6968,20 +6970,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) {
    @@ -7058,9 +7052,7 @@ private CreateSequence parseCreateSequence() {
         }
     
         private boolean readIfNotExists() {
    -        if (readIf(IF)) {
    -            read(NOT);
    -            read(EXISTS);
    +        if (readIf(IF, NOT, EXISTS)) {
                 return true;
             }
             return false;
    @@ -7117,8 +7109,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
    @@ -7158,8 +7149,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")) {
    @@ -7200,8 +7190,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 {
    @@ -7429,6 +7418,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);
    @@ -7437,20 +7428,21 @@ private TableView parseSingleCommonTableExpression(boolean isTemporary) {
                     withQuery.session = session;
                 }
                 read(CLOSE_PAREN);
    -            columnTemplateList = TableView.createQueryColumnTemplateList(cols, withQuery, querySQLOutput);
    +            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();
    @@ -7463,8 +7455,8 @@ private TableView createCTEView(String cteViewName, String querySQL,
             TableView view;
             synchronized (session) {
                 view = new TableView(schema, id, cteViewName, querySQL,
    -                    parameters, columnTemplateArray, session,
    -                    allowRecursiveQueryDetection, false /* literalsChecked */, true /* isTableExpression */,
    +                    queryParameters, columnTemplateArray, session,
    +                    allowRecursiveQueryDetection, false, true,
                         isTemporary);
                 if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) {
                     if (!isTemporary) {
    @@ -7474,9 +7466,9 @@ 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 /* literalsChecked */, true /* isTableExpression */,
    +                        false/* assume recursive */, false, true,
                             isTemporary);
                 }
                 // both removeSchemaObject and removeLocalTempTable hold meta locks
    @@ -7643,8 +7635,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);
    @@ -7681,8 +7672,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);
    @@ -7702,8 +7692,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());
    @@ -7765,8 +7754,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);
    @@ -7803,6 +7791,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")) {
    @@ -7858,8 +7847,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));
    @@ -8528,8 +8516,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);
    @@ -8547,8 +8534,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);
    @@ -8602,9 +8588,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 {
    @@ -8612,8 +8597,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) {
    @@ -8642,8 +8625,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(
    @@ -8661,8 +8643,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;
    @@ -8670,8 +8651,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);
    @@ -8705,8 +8685,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);
    @@ -8752,8 +8731,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());
    @@ -9046,8 +9024,7 @@ private ConstraintActionType parseAction() {
             if (result != null) {
                 return result;
             }
    -        if (readIf("NO")) {
    -            read("ACTION");
    +        if (readIf("NO", "ACTION")) {
                 return ConstraintActionType.RESTRICT;
             }
             read(SET);
    @@ -9109,8 +9086,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());
    @@ -9119,16 +9095,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();
    @@ -9219,9 +9195,7 @@ private void parseReferences(AlterTableAddConstraint command,
                     command.setUpdateAction(parseAction());
                 }
             }
    -        if (readIf(NOT)) {
    -            read("DEFERRABLE");
    -        } else {
    +        if (!readIf(NOT, "DEFERRABLE")) {
                 readIf("DEFERRABLE");
             }
         }
    @@ -9254,8 +9228,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);
    @@ -9309,8 +9282,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")) {
    @@ -9327,8 +9299,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")) {
    @@ -9400,8 +9371,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,
    @@ -9491,9 +9461,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");
                     }
    @@ -9544,8 +9512,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;
    @@ -9639,7 +9606,7 @@ public void setRightsChecked(boolean rightsChecked) {
         }
     
         public void setSuppliedParameters(ArrayList suppliedParameters) {
    -        this.suppliedParameters = suppliedParameters;
    +        this.parameters = suppliedParameters;
         }
     
         /**
    @@ -9649,7 +9616,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();
    @@ -9662,7 +9628,6 @@ public Expression parseExpression(String sql) {
          * @return the expression object
          */
         public Expression parseDomainConstraintExpression(String sql) {
    -        parameters = Utils.newSmallArrayList();
             initialize(sql, null, false);
             read();
             try {
    @@ -9680,7 +9645,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/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/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/command/ddl/CreateView.java b/h2/src/main/org/h2/command/ddl/CreateView.java
    index dc397ae3da..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/* allow recursive */, false/* literalsChecked */, isTableExpression, false/*temporary*/);
    +                        false, false, isTableExpression, false);
                 }
             } else {
                 // TODO support isTableExpression in replace function...
    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/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/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/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/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..5d45222fca 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;
    @@ -207,6 +207,22 @@ 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;
    +        }
    +        prepareExpressions();
    +        preparePlan();
    +    }
    +
    +    public abstract void prepareExpressions();
    +
    +    public abstract void preparePlan();
    +
         /**
          * The the list of select expressions.
          * This may include invisible expressions such as order by expressions.
    @@ -431,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;
                 }
             }
    @@ -446,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;
         }
    @@ -983,8 +1001,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,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");
    -        }
    +    public void prepareExpressions() {
             if (orderList != null) {
                 prepareOrder(orderList, expressions.size());
             }
    @@ -1182,25 +1175,30 @@ public void prepare() {
             }
             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 &&
    @@ -1270,19 +1268,28 @@ public void prepare() {
                     }
                 }
             }
    -        expressionArray = expressions.toArray(new Expression[0]);
             isPrepared = true;
         }
     
         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);
             }
         }
     
    @@ -1495,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/main/org/h2/command/query/SelectUnion.java b/h2/src/main/org/h2/command/query/SelectUnion.java
    index a1388eccfe..978a22585c 100644
    --- a/h2/src/main/org/h2/command/query/SelectUnion.java
    +++ b/h2/src/main/org/h2/command/query/SelectUnion.java
    @@ -246,17 +246,9 @@ 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;
    -        left.prepare();
    -        right.prepare();
    +    public void prepareExpressions() {
    +        left.prepareExpressions();
    +        right.prepareExpressions();
             int len = left.getColumnCount();
             // set the correct expressions now
             expressions = new ArrayList<>(len);
    @@ -279,6 +271,13 @@ public void prepare() {
             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 82d171fa3c..713b154d86 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;
    +    public void prepareExpressions() {
             if (columnResolver == null) {
                 createTable();
             }
    @@ -200,6 +192,10 @@ public void prepare() {
                 cleanupOrder();
             }
             expressionArray = expressions.toArray(new Expression[0]);
    +    }
    +
    +    @Override
    +    public void preparePlan() {
             double cost = 0;
             int columnCount = visibleColumnCount;
             for (ArrayList r : rows) {
    @@ -208,6 +204,7 @@ public void prepare() {
                 }
             }
             this.cost = cost + rows.size();
    +        isPrepared = true;
         }
     
         private void createTable() {
    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/Constants.java b/h2/src/main/org/h2/engine/Constants.java
    index d71cf6b656..0e42e582c2 100644
    --- a/h2/src/main/org/h2/engine/Constants.java
    +++ b/h2/src/main/org/h2/engine/Constants.java
    @@ -15,13 +15,13 @@ 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-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 = 210;
    +    public static final int BUILD_ID = 214;
     
         /**
          * Whether this is a snapshot version.
    @@ -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 = 1000_000_000; /** * The maximum allowed precision of numeric data types. diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java index f7d6958c4e..f9d3566eff 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; @@ -131,7 +130,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<>(); @@ -208,7 +206,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(); @@ -227,7 +225,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; @@ -272,6 +269,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); @@ -321,7 +321,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(); } @@ -1181,9 +1181,6 @@ private void closeImpl(boolean fromShutdownHook) { closeAllSessionsExcept(null); } } - if (!this.isReadOnly()) { - removeOrphanedLobs(); - } } try { try { @@ -1192,7 +1189,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); } @@ -1217,22 +1214,16 @@ private void closeImpl(boolean fromShutdownHook) { meta.close(systemSession); systemSession.commit(true); } - } - } catch (DbException e) { - 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"); @@ -1253,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(); @@ -1719,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; } @@ -2365,10 +2348,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/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/Mode.java b/h2/src/main/org/h2/engine/Mode.java index 26f875b976..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 @@ -503,6 +513,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; @@ -645,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); @@ -672,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/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/SessionLocal.java b/h2/src/main/org/h2/engine/SessionLocal.java index 8117c628da..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()) { @@ -630,7 +646,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(); } @@ -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/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/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/engine/User.java b/h2/src/main/org/h2/engine/User.java index 312516a84f..80e1c3c4b5 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; @@ -75,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); @@ -221,15 +215,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/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/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/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/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/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(); 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/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..45f7aebb3a 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; @@ -654,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; } @@ -713,7 +717,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/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; } 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/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/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/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/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/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/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/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/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/FileStore.java b/h2/src/main/org/h2/mvstore/FileStore.java index dc1142fcac..ef30c36fa9 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,21 @@ 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 +158,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/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 46daadd11e..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; @@ -39,6 +38,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; @@ -225,7 +225,7 @@ public class MVStore implements AutoCloseable { private final FileStore fileStore; - private final boolean fileStoreIsProvided; + private final boolean fileStoreShallBeClosed; private final int pageSplitSize; @@ -364,6 +364,11 @@ public class MVStore implements AutoCloseable { private long leafCount; private long nonLeafCount; + /** + * Callback for maintenance after some unused store versions were dropped + */ + private volatile LongConsumer oldestVersionTracker; + /** * Create and open the store. @@ -378,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; @@ -432,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); } @@ -580,7 +588,7 @@ private void unlockAndCheckPanicCondition() { } } - private void panic(MVStoreException e) { + public void panic(MVStoreException e) { if (isOpen()) { handleException(e); panicException = e; @@ -932,6 +940,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; } @@ -950,8 +959,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; } } @@ -1125,6 +1132,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; @@ -1298,6 +1310,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) { @@ -1341,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(); } } @@ -1355,18 +1368,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. * @@ -1543,7 +1544,7 @@ private static void submitOrRun(ThreadPoolExecutor executor, Runnable action, return; } catch (RejectedExecutionException ex) { assert executor.isShutdown(); - shutdownExecutor(executor); + Utils.shutdownExecutor(executor); } } action.run(); @@ -2027,13 +2028,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) { @@ -2724,6 +2725,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. @@ -2766,14 +2775,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); + + if (oldestVersionTracker != null) { + oldestVersionTracker.accept(version); + } + } + + public void setOldestVersionTracker(LongConsumer callback) { + oldestVersionTracker = callback; } private long lastChunkVersion() { @@ -3125,6 +3142,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(); } @@ -3329,8 +3351,7 @@ public boolean isClosed() { } storeLock.lock(); try { - assert state == STATE_CLOSED; - return true; + return state == STATE_CLOSED; } finally { storeLock.unlock(); } @@ -3361,9 +3382,9 @@ private void stopBackgroundThread(boolean waitForIt) { } } } - shutdownExecutor(serializationExecutor); + Utils.shutdownExecutor(serializationExecutor); serializationExecutor = null; - shutdownExecutor(bufferSaveExecutor); + Utils.shutdownExecutor(bufferSaveExecutor); bufferSaveExecutor = null; break; } @@ -3398,22 +3419,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(); } @@ -3577,21 +3588,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 void onVersionChange(long version) { TxCounter txCounter = currentTxCounter; assert txCounter.get() >= 0; @@ -3607,7 +3627,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() { @@ -3800,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; } @@ -4047,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 ae7f5e4f37..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.message.DbException; import org.h2.mvstore.tx.TransactionStore; import org.h2.mvstore.type.BasicDataType; import org.h2.mvstore.type.StringDataType; @@ -440,14 +439,25 @@ public static void compact(String fileName, boolean compress) { String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; FileUtils.delete(tempName); compact(fileName, tempName, compress); + moveAtomicReplace(tempName, fileName); + } + + /** + * Rename a file(s) of the named store, and try to atomically replace an + * existing file(s) of another store. + * + * @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 moveAtomicReplace(String sourceName, String destinationName) { try { - FileUtils.moveAtomicReplace(tempName, fileName); - } catch (DbException e) { - String newName = fileName + Constants.SUFFIX_MV_STORE_NEW_FILE; + FileUtils.moveAtomicReplace(sourceName, destinationName); + } catch (MVStoreException e) { + 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); } } @@ -508,6 +518,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 { diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java index 16d74229ae..ac4624e871 100644 --- a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -15,6 +15,11 @@ 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.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; @@ -22,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; @@ -34,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; @@ -53,6 +60,8 @@ public final class LobStorageMap implements LobStorageInterface private final Database database; 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 @@ -78,6 +87,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 +112,24 @@ public LobStorageMap(Database database) { Store s = database.getStore(); TransactionStore txStore = s.getTransactionStore(); mvStore = s.getMvStore(); + 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); @@ -355,7 +383,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 +398,7 @@ public void removeAllForTable(int tableId) { } } for (long lobId : list) { - removeLob(tableId, lobId); + doRemoveLob(tableId, lobId); } } } finally { @@ -380,18 +408,50 @@ 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 boolean needCleanup() { + return !pendingLobRemovals.isEmpty(); + } + + @Override + 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 { - 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); + // we can not call deregisterVersionUsage() due to a possible infinite recursion + mvStore.decrementVersionUsageCounter(txCounter); } } - private void removeLob(int tableId, long lobId) { + private void doRemoveLob(int tableId, long lobId) { if (TRACE) { trace("remove " + tableId + "/" + lobId); } @@ -560,4 +620,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/main/org/h2/mvstore/db/MVTempResult.java b/h2/src/main/org/h2/mvstore/db/MVTempResult.java index 97779cba55..26735b4fa5 100644 --- a/h2/src/main/org/h2/mvstore/db/MVTempResult.java +++ b/h2/src/main/org/h2/mvstore/db/MVTempResult.java @@ -13,8 +13,8 @@ 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; import org.h2.result.SortOrder; import org.h2.store.fs.FileUtils; @@ -176,11 +176,10 @@ 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); + 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/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java index 6f5b5befcf..7b71992492 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. @@ -48,7 +52,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; } @@ -81,14 +85,15 @@ static char[] decodePassword(byte[] key) { * Creates the store. * * @param db the database + * @param key for file encryption */ - 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; if (dbPath != null) { String fileName = dbPath + Constants.SUFFIX_MV_FILE; + this.fileName = fileName; MVStoreTool.compactCleanUp(fileName); builder.fileName(fileName); builder.pageSplitSize(db.getPageSize()); @@ -123,12 +128,12 @@ public Store(Database db) { // 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); } @@ -152,6 +157,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); @@ -167,6 +174,22 @@ 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; } @@ -351,13 +374,21 @@ public void close(int allowedCompactionTime) { 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); + compact(fileName, targetFileStore); } } } catch (MVStoreException e) { @@ -372,6 +403,20 @@ public void close(int allowedCompactionTime) { } } + + 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/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/rtree/MVRTreeMap.java b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java index 4b8a7a60c1..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); } @@ -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); } @@ -234,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)); } @@ -321,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) { @@ -348,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; @@ -367,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))); } } 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 { 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/res/help.csv b/h2/src/main/org/h2/res/help.csv index d783fa770a..fa6b620fa3 100644 --- a/h2/src/main/org/h2/res/help.csv +++ b/h2/src/main/org/h2/res/help.csv @@ -958,9 +958,13 @@ 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. +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, @@ -2400,105 +2404,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 " @@ -3830,7 +3855,7 @@ CURRENT ROW | ( expression ) | arrayElementReference | fieldReference - | query + | ( query ) | caseExpression | castSpecification | userDefinedFunctionName } @@ -3878,7 +3903,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. @@ -3904,14 +3929,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. @@ -3928,8 +3952,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. @@ -3959,7 +3982,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. @@ -3977,7 +4000,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. @@ -3999,13 +4022,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. @@ -4020,8 +4042,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. @@ -4038,7 +4059,7 @@ BLOB(10K) " "Data Types","BOOLEAN Type"," -BOOLEAN | @c@ { BIT | BOOL } +BOOLEAN "," Possible values: TRUE, FALSE, and UNKNOWN (NULL). @@ -4066,7 +4087,7 @@ TINYINT " "Data Types","SMALLINT Type"," -SMALLINT | @c@ { INT2 } +SMALLINT "," Possible values: -32768 to 32767. @@ -4082,7 +4103,7 @@ SMALLINT " "Data Types","INTEGER Type"," -INTEGER | INT | @c@ { MEDIUMINT | INT4 | SIGNED } +INTEGER | INT "," Possible values: -2147483648 to 2147483647. @@ -4094,7 +4115,7 @@ INT " "Data Types","BIGINT Type"," -BIGINT | @c@ INT8 +BIGINT "," Possible values: -9223372036854775808 to 9223372036854775807. @@ -4120,7 +4141,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. @@ -4133,7 +4154,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. @@ -4231,11 +4252,9 @@ 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. -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. @@ -4306,7 +4325,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. @@ -4337,8 +4356,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') " @@ -4366,7 +4385,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). @@ -4389,7 +4409,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 @@ -5106,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) "," @@ -5404,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. "," @@ -5414,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. "," @@ -5680,7 +5687,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 +5740,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 +5759,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/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/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/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/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/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(); 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..acff786ecd 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; @@ -705,7 +707,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); @@ -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,43 @@ 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)); + } + if (sign != NUMERIC_POSITIVE) { + n = n.negate(); + } + 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(); 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/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; 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/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); } } 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/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..c1831f402b 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()); } @@ -303,6 +311,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(); @@ -432,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(); 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..1309450f38 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,41 @@ 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; + source = null; + } + 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/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/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..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", @@ -2403,7 +2404,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/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/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

  • 2.1.214Windows InstallerPlatform-Independent Zip
    2.1.212Windows InstallerPlatform-Independent Zip
    2.1.210 Windows Installer Platform-Independent Zip
    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/Table.java b/h2/src/main/org/h2/table/Table.java index c2b5b14fbc..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() @@ -760,7 +755,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/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/main/org/h2/table/TableView.java b/h2/src/main/org/h2/table/TableView.java index eba1b12fa6..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,45 +15,28 @@ 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; @@ -63,7 +44,7 @@ public class TableView extends Table { public TableView(Schema schema, int id, String name, String querySQL, ArrayList params, Column[] columnTemplates, SessionLocal session, boolean allowRecursive, boolean literalsChecked, boolean isTableExpression, boolean isTemporary) { - super(schema, id, name, false, true); + super(schema, id, name); setTemporary(isTemporary); init(querySQL, params, columnTemplates, session, allowRecursive, literalsChecked, isTableExpression); } @@ -102,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); } @@ -165,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 = 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) { @@ -218,11 +180,6 @@ private void initColumnsAndTables(SessionLocal session, boolean literalsChecked) } } - @Override - public boolean isView() { - return true; - } - /** * Check if this view is currently invalid. * @@ -233,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 @@ -326,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; @@ -414,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, @@ -431,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() { @@ -479,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 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/* allow recursive */, true /* literals have already been checked when parsing original query */, - false /* is table expression */, true/*temporary*/); - 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; } @@ -557,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) { @@ -571,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. * @@ -690,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 { @@ -725,40 +453,6 @@ public static TableView createTableViewMaybeRecursive(Schema schema, int id, Str 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/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/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); 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/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 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/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/main/org/h2/value/TypeInfo.java b/h2/src/main/org/h2/value/TypeInfo.java index cc607a239b..fb1d7b77c9 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,46 @@ 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) { + 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; + 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()); + } + } else { + return type2.getValueType() == Value.GEOMETRY ? TypeInfo.TYPE_GEOMETRY : type1; + } + } else if (ext2 instanceof ExtTypeInfoGeometry) { + return type1.getValueType() == Value.GEOMETRY ? TypeInfo.TYPE_GEOMETRY : type2; + } else { + return TYPE_GEOMETRY; + } + return new TypeInfo(Value.GEOMETRY, -1L, -1, new ExtTypeInfoGeometry(t, srid)); + } + private static int dimensions(TypeInfo type) { int result; for (result = 0; type.getValueType() == Value.ARRAY; result++) { 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/main/org/h2/value/ValueBinary.java b/h2/src/main/org/h2/value/ValueBinary.java index ef160e4665..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; /** @@ -22,13 +20,8 @@ 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) { - throw DbException.getValueTooLongException(getTypeName(getValueType()), - StringUtils.convertBytesToHex(value, 41), length); - } } /** 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; 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 9eb3a75d29..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; /** @@ -19,13 +17,8 @@ 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) { - 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/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..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; /** @@ -27,13 +25,8 @@ 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) { - throw DbException.getValueTooLongException(getTypeName(getValueType()), - StringUtils.convertBytesToHex(value, 41), length); - } } /** 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; - } - } 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; 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 + ")"); } diff --git a/h2/src/test/org/h2/samples/newsfeed.sql b/h2/src/test/org/h2/samples/newsfeed.sql index 82c483be5c..e00f43466c 100644 --- a/h2/src/test/org/h2/samples/newsfeed.sql +++ b/h2/src/test/org/h2/samples/newsfeed.sql @@ -7,6 +7,8 @@ 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'), (152, '2.0.204', '2021-12-21'), @@ -18,9 +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'), -(141, '1.4.191', '2016-01-21'); +(143, '1.4.193', '2016-10-31'); CREATE TABLE CHANNEL(TITLE VARCHAR, LINK VARCHAR, DESC VARCHAR, LANGUAGE VARCHAR, PUB TIMESTAMP, LAST TIMESTAMP, AUTHOR VARCHAR); 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/test/org/h2/test/db/TestFunctions.java b/h2/src/test/org/h2/test/db/TestFunctions.java index dd601a5050..30c66e9961 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; @@ -42,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; @@ -76,6 +79,8 @@ public class TestFunctions extends TestDb implements AggregateFunction { static int count; + private static HashSet RESULT_SETS = new HashSet<>(); + /** * Run just this test. * @@ -130,8 +135,10 @@ public void test() throws Exception { testThatCurrentTimestampIsSane(); testThatCurrentTimestampStaysTheSameWithinATransaction(); testThatCurrentTimestampUpdatesOutsideATransaction(); + testCompatibilityDateTime(); testAnnotationProcessorsOutput(); testSignal(); + testLegacyDateTime(); deleteDb("functions"); } @@ -153,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(); @@ -193,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; } @@ -1890,6 +1908,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 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(ldt, odt0.toLocalDateTime()); + stat.execute("SET TIME ZONE '2:00'"); + rs = stat.executeQuery("SELECT SYSDATE, SYSTIMESTAMP, SYSTIMESTAMP(0), SYSTIMESTAMP(9)"); + rs.next(); + 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)); + conn.close(); + } finally { + TimeZone.setDefault(tz); + } + } + private void testThatCurrentTimestampIsSane() throws SQLException, ParseException { deleteDb("functions"); @@ -1971,6 +2019,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 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(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(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)); + } + conn.close(); + } + } finally { + TimeZone.setDefault(tz); + } + } + + private void testOverrideAlias() throws SQLException { deleteDb("functions"); Connection conn = getConnection("functions"); 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(); + } + } diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java index 45203921a2..0c660fc789 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(); @@ -116,7 +117,8 @@ public void test() throws Exception { testLob(true); testJavaObject(); testLobInValueResultSet(); - testLimits(); + // cannot run this on CI, will cause OOM + // testLimits(); deleteDb("lob"); } @@ -252,8 +254,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 +1581,48 @@ 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")) { + try (JdbcConnection conn2 = (JdbcConnection) getConnection("lob")) { + + try (Statement st = conn1.createStatement()) { + String createTable = "create table t1 (id int, ver bigint, data text, primary key (id));"; + st.execute(createTable); + } + + 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(); + } + + long startTimeNs = System.nanoTime(); + + 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); + } + }); + thread1.start(); + + try (PreparedStatement st = conn1.prepareStatement("select * from t1 where id = 1")) { + while (System.nanoTime() - startTimeNs < 10_000_000_000L) { + st.executeQuery(); + } + } + thread1.join(); + } + } + } } 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"); + } + } + } diff --git a/h2/src/test/org/h2/test/db/TestTableEngines.java b/h2/src/test/org/h2/test/db/TestTableEngines.java index a87646f7e3..e81d72d7f3 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", getPassword("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"); 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 { 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 a05dec4eba..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; @@ -747,5 +762,11 @@ EXPLAIN SELECT * FROM TEST WHERE I = TRUE; DROP TABLE TEST; > ok +SET MODE Oracle; +> ok + +SELECT (SELECT * FROM (SELECT SYSDATE)) IS NOT NULL; +>> TRUE + SET MODE Regular; > ok 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/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 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/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/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 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..5ef2e589ab 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,75 @@ 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 + +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 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 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/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 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..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 @@ -49,23 +56,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/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 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 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 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 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 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 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 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 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; 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)); 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); 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 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/Build.java b/h2/src/tools/org/h2/build/Build.java index 7d23858a3b..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.2.14"; + private static final String PGJDBC_VERSION = "42.4.0"; - private static final String PGJDBC_HASH = "45fa6eef266aa80024ef2ab3688d9faa38c642e5"; + private static final String PGJDBC_HASH = "21ff952426bbfe4a041c175407333d4a07c70931"; private static final String JAVAX_SERVLET_VERSION = "4.0.1"; @@ -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")); } /** @@ -611,7 +618,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 +658,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" + diff --git a/h2/src/tools/org/h2/build/doc/dictionary.txt b/h2/src/tools/org/h2/build/doc/dictionary.txt index 9e48dba39b..3724b9c4cf 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 +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 symlink respected adopted 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; } } 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